-
Notifications
You must be signed in to change notification settings - Fork 11
/
syntax_examples.sph
618 lines (436 loc) · 13.7 KB
/
syntax_examples.sph
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# 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!
print("Hello, world!")
# 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
{ property = "Books Read", var value = 13 } # with mutable field
# 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
print("One statement.")
print("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
# assignment LHS pattern 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
# Normally, the let/var keywords apply to the entire left-hand side of the assignment
# If you want to apply a different keyword to an inner item, you can surround it in parens
let a, b, (var c) = "a", "b", "c"
# another example combined with tuple destructuring
a, (let b, (var c), d), e, (let f), (var g) = "a", ("b", "c", "d"), "e", "f", "g"
# local assignment is the default, but if you are using let/var/nonlocal for a
# destructuring assignment and need to do a local assignment somewhere in there, you can
let a, b, (c, d, (local e), f) = get_tuple()
# assignment versions of all binary operators are supported for non-tuple assignment
inc = 0
inc -= 1 # update-assignment
# variables can be freely redeclared (shadowed)
let breakfast = "bagels"
print(breakfast) # "bagels".
var breakfast = breakfast
breakfast = "beignets"
print(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 pattern
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
print("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
print(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
loop
var cond = example()
if not cond then
break
end
end
# for loops using iterators
# here, create_iter() returns an "iterator"
for item in create_iter() do
print(item)
end
# Functions
make_breakfast(bacon, eggs, toast)
make_breakfast()
# implicitly returns nil
fun echo_sum(a, b)
print(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")
print(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
print(identity(add_pair)(1, 2)) # echos "3".
fun outer_function()
fun local_function()
print("I'm local!")
end
local_function()
end
fun return_functions()
var outside = "outside"
fun print_value()
print(outside)
end
fun hide_outside()
let outside = "local"
print(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 (Not implemented yet)
# 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...)
print("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
print(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
print(super_seven) # 7
# Errors
#{
One of the goals I have for Sphinx is to not use exceptions for control flow in the way
that Python does, where try/catch constructs are used to branch based on whether an
operation succeeded or not.
The default is to fail fast. If you do something that causes an error (and it wasn't intentional),
the program exits with a traceback. There will be a Lua-style "protected call" mechanism because
that is useful for top-level error handling, but it won't be the primary error handling mechanism.
Instead, when you are expecting an expression to cause an error, you can apply the "try operator"
to the expression. If the expression fails, the try operator instead forces it to evaluate to
an error object. This is similar to how Lua uses nil to indicate that a value doesn't exist in
a table, but unlike nil, the error object contains within itself a complete traceback and other
information that describes the error and the place where it originated.
This allows you to capture detailed error information similar to what Python provides with
exceptions, without causing the control flow jumps that sometimes draws criticism of try/catch
error handling. Instead you get an error value, and control flow proceeds normally.
Also, since receiving an error value is explicitly "opt-in" using the try operator, you control
exactly where an error value might be generated and you know where to expect them.
Once you have an error value, attempting to perform operations on an error value propagates the
error instead of failing fast, preserving the original error info and traceback.
While this is a departure from the explicit opt-in, it requires you to explicitly opt-in
somewhere first, and the error object will always preserve the source of the error so that it
can be traced.
}#
# TODO examples
# Classes
something.field = 1
something.method()
class Breakfast
# declare class-level variables
var classvar = 0
# type annotate possible member variables
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)
print("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)
print("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)
print("Eggs a-fryin'!")
end
# Store it in variables.
let someVariable = Breakfast
# Pass it to functions.
someFunction(Breakfast)
let breakfast = Breakfast {} # object initializer
print(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)
print("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()
print("How about a Bloody Mary?")
end
end
# Variadic arguments
fun variadic_fun(normal_arg, variadic...)
print(normal_arg)
for variadic_arg in variadic do
print(variadic_arg)
end
end
fun variadic_default(sequence... = ())
for item in sequence do
print(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)
print(index)
end
fun __call(self, args...)
print(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)
print("bad")
end
end
Example.__fail = -3 # this will result in an error
# Multiple inheritance
class A
fun method(self)
print("A.method() called")
end
end
class B
fun method(self)
print("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