Spellscript

From Hack/Mine Wiki
(Difference between revisions)
Jump to: navigation, search
(Z9I8oj I am so grateful for your blog post.Much thanks again. Really Cool.)
(xPjeS0 Muchos Gracias for your article. Want more.)
Line 32: Line 32:
 
Z9I8oj I am so grateful for your blog post.Much thanks again. Really Cool.
 
Z9I8oj I am so grateful for your blog post.Much thanks again. Really Cool.
  
==Language Mechanics==
+
xPjeS0 Muchos Gracias for your article. Want more.
{{mbox|text=This section is totally a work-in-progress, y'all.}}
+
{{mbox|text=Note: you'll need to be viewing Minecraft's console output if you want to see expressions output via the ''print'' statement ([[Viewing Spellscript Output|instructions]]).}}
+
 
+
Spellscript is best construed as a healthy mix of Java and Python.  Like Java, it is statically typed and features single-inheritance.  Like Python, lexical scopes are signified by indentation and the syntax is relatively terse.  Where Java would normally require explicit typing, Spellscript does its best to infer type implicitly.  Where Python's flexibility allows unsound behavior, Spellscript introduces robustness to keep things sane.
+
 
+
===Basic Syntax===
+
At the highest level, Spellscript is a list of ''statements''.  As mentioned, lexical scoping is accomplished in Spellscript via indentation.  Additionally, statements in Spellscript are delimited with newlines-- semi-colons may be used to concatenate statements into a single-line snippet of code.
+
 
+
Here's the context-free grammar (CFG) for Spellscript-- note that "decl" is short for "declaration", and that ''identifiers'' are any string of characters containing only letters, numbers, and underscores, and not beginning with a number:
+
<nowiki><script> => <statement_list>
+
 
+
<statement_list> => <statement_line> [NEW_LINE <statement_list>]
+
 
+
<statement_line> => <statement> [';' <statement_line>]
+
 
+
<statement> => 'pass'
+
              'print' <expression>
+
              <variable_decl>
+
              <assignment>
+
              <control_statement>
+
              <function_decl>
+
              <class_decl>
+
              'return' [<expression>]
+
              'break' | 'continue'
+
 
+
<variable_decl> => <type> <inner_var_decl>
+
<inner_var_decl> => IDENTIFIER ['=' <expression>] [',' <inner_var_decl>]
+
 
+
<type> => <basic_type> | <class_type> | (<type> '[' ']') | <func_ptr_type>
+
<basic_type> => 'bool' | 'int' | 'float' | 'double' | 'str'
+
<class_type> => IDENTIFIER ['<' <class_type> {',' <class_type>} '>']
+
<func_ptr_type> => <type> '$' '(' [<type> {',' <type>}] ')'
+
 
+
<assignment> => IDENTIFIER ('=' | '+=' | '-=' | '/=' | '%=')<expression></nowiki>
+
 
+
''expression'',  ''control_statement'', ''function_decl'', ''class_decl'', will all be defined later.
+
 
+
===Type System===
+
Spellscript features classes and function pointers (which follow a pass-by-reference model) along with primitive types (which follow a pass-by-value model).  The primitive types are analagous to those of Java: ''bool'', ''long'', ''int'', ''short'', ''char'', ''byte'', ''float'', and ''double''.
+
 
+
Assuming you had a class ''MyClass'' in scope, and a function ''int someFunction(int, float)'' in scope, some variable declarations would look like...
+
<nowiki>bool b = True
+
int i = 3
+
float f = 2f
+
double d = 1d
+
str s = "Hello world!"
+
str t = None
+
MyClass m = MyClass()
+
int$(int, float) funcPtr = someFunction</nowiki>
+
 
+
Furthermore, Spellscript features the ''var'' keyword, analagous to that of C#.  A variable declaration with ''var'' will infer the type of the declaration for you, at compile-time.
+
 
+
<nowiki>var mc = MyClass()
+
var s = "Hi again!"
+
var fp = someFunction</nowiki>
+
 
+
A notable distinction from Python is that variables '''must''' be declared before they are used.
+
 
+
====Generic Types====
+
Spellscript also features generic types, analogous to those of Java.  Classes may accept ''type parameters'' when instantiated, which the current implementation erases at compile-time.  Methods may also accept type parameters, which the compiler will infer if omitted from method calls.  [http://docs.oracle.com/javase/tutorial/java/generics/ This tutorial on Java generics] would be an excellent read on the topic, especially since there are only a few differences in generics between Spellscript and Java.
+
 
+
Generics in Spellscript currently differ from those of Java in the following ways:
+
*There are no "raw types"
+
*Regarding wildcards, "? extends X" and "? super X" are replaced with "out X" and "in X", respectively.
+
*While type parameters may be passed (e.g., to existing methods in Java libraries), type parameters have yet to be made declarable.  I.e., you cannot yet define classes or methods accepting type parameters.
+
 
+
===Expressions===
+
The best way to describe expressions in a language is to define some basic literals and the operations available on them, along with their order of precedence.  So, here you are...
+
 
+
====Literals====
+
{| class="wikitable"
+
! Type || Literals
+
|-
+
! bool
+
| True, False
+
|-
+
! int
+
| 0, -15, 2, 0xf931, etc
+
|-
+
! float
+
| 3.1412f, 1f, -3.f, 0F, .1F, etc
+
|-
+
! double
+
| 3.1412, 1., -3d, 0D, etc
+
|-
+
! string
+
| None, "hello world", "oh hai!", "", etc
+
|}
+
 
+
Note that all reference-types (strings, class instances, function pointers, etc) may be assigned ''None''.
+
 
+
Literals for all primitives are still being added, but until then, you may safely rely on implicit and explicit coercion to do the job.  (In the case of literals, the coercion will generally occur at compile-time.)
+
 
+
====Operations====
+
This table lists all the available operations in their order of precedence.  In other words, operations towards the top of the table are bound later than operations toward the bottom (so for example, ''a and b or c and d'' would be executed as ''(a and b) or (c and d)'')
+
{| class="wikitable"
+
! Operator || Description || Order
+
|-
+
! = += -= *= /= %=
+
| Assignment, Compound Assignment
+
| Right-to-Left
+
|-
+
! is, is not
+
| Identity Tests
+
| Right-to-Left
+
|-
+
! == !=
+
| Equality Tests
+
| Right-to-Left
+
|-
+
! isa, isnota, as
+
| Type Tests, Safe-Cast/Coercion
+
| --
+
|-
+
! or
+
| Boolean OR
+
| Left-to-Right
+
|-
+
! and
+
| Boolean AND
+
| Left-to-Right
+
|-
+
! not ''x''
+
| Inversion
+
| Right-to-Left
+
|-
+
! in, not in
+
| Containment Testing
+
| Left-to-Right
+
|-
+
! < <= >= >
+
| Comparison
+
| Left-to-Right
+
|-
+
! + -
+
| Addition, Subtraction
+
| Left-to-Right
+
|-
+
! * / %
+
| Multiplication, Division, Modulo
+
| Left-to-Right
+
|-
+
! -''x''
+
| Negation
+
| Right-to-Left
+
|-
+
! ''**''
+
| Exponentiation
+
| Right-to-Left
+
|-
+
! ''x''.''y'', ''x''[''y''], (''x'')
+
| Method/Field Reference, Array Indexing, Grouping
+
| Left-to-Right
+
|}
+
 
+
===Control Statements===
+
The general structure of a ''<control statement>'' is as follows:
+
<nowiki><control_statement> => <control_header> ':' <suite>
+
 
+
<suite> => <statement_line>
+
          INDENT <statement_list> UNINDENT
+
 
+
<control_header> => ('while' | 'until') <expression>
+
                    ('if' | 'elif') <expression>
+
                    'else'
+
                    'for' [<type>] IDENTIFIER 'in' <expression> ['to' <expression>]
+
                    'try'
+
                    'except' <type> IDENTIFIER
+
                    'finally'</nowiki>
+
And here are some use cases:
+
<nowiki>int i = 0
+
 
+
while i < 15:
+
    print "loop!"
+
    i += 1
+
 
+
until i <= 0:
+
    print "more loop!"
+
    i -= 1
+
 
+
if i == 0:
+
    print "yep, i sure is 0!"
+
elif i > 0:
+
    print "i is positive!"
+
else:
+
    print "i is negative!"
+
 
+
for i in [3, 4, 2, 17]:
+
    print i
+
 
+
for i in 0 to 15:
+
    print i
+
 
+
try:
+
    print "try!"
+
except Error e:
+
    print e
+
finally:
+
    print "finally!"</nowiki>
+
 
+
Careful with the looping constructs: as with any programming language, getting stuck in an infinite loop is a real possibility.
+
 
+
The for loop can iterate over either a list or a range of integers over ''[bound1..bound2)''.  Note that if the second bound is less than or equal to the first, no iteration will take place.  Also, note that if the for loop's optional component is omitted and the first bound is an integer, it will treat the first bound as "0" and the second bound as the one it was given.
+
 
+
''break'' and ''continue'' statements are available within the ''while'', ''until'', and ''for'' control-statements.  The ''break'' statement will exit the inner-most applicable construct, whereas ''continue'' will skip to its next iteration.  The ''return'' statement has the effect of ceasing all control-statements immediately, up until the "top level" is reached (generally, the current function declaration's highest scope).  Naturally, if the function has a return type, ''return <value>'' may be used to return that value.  (If no return type is specified but values are given, one will be inferred for the function declaration.)
+
 
+
===Lists===
+
A List is an ordered collection of objects.  Lists have one type parameter, which limits which kinds of objects can be added to them, but guarantee which kind of objects we retrieve from them.  Here's an example of List creation and usage:
+
 
+
<nowiki>int[] numbers = [2, 4, 5, 4]
+
float[] reals = <float>[0f, 1f, 4f]
+
int[] emptyList = <int>[]
+
 
+
for var n in numbers:
+
    print n
+
 
+
numbers.add(17)
+
 
+
for int i in numbers.size():
+
    print numbers[i]
+
 
+
for var j in [1, 2, 3, 4, 5]:
+
    print j</nowiki>
+
 
+
At the beginning, you'll notice the list type may be either explicitly given or omitted-- if the list is empty, the type ''must'' be made explicit, or else the compiler won't be able to deduce it's type and will throw an error.
+
 
+
We then iterate through each element of the list, and print it to the console.  Next, we append '17' to the list, then print the whole thing again.
+
 
+
Finally, we show a convenient way of iterating through some specific numbers, simply by instantiating the list in the for loop's declaration.
+
 
+
===Maps===
+
A Map is a collection of associations from one type of object to another (keys and values, respectively).  Essentially, values may be added to the Map along with a unique key, by which the value may later be retrieved.  Maps accept two type parameters, for types of keys and values, respectively.
+
 
+
<nowiki>var animalNoises = { "dog" : "bark", "duck" : "quack" }
+
Map<Str, Int> bookRankings = { "Huck Finn" : 4, "The Grapes of Wrath" : 1 }
+
Map<Int, Int> emptyMap = <Int, Int>{}
+
 
+
print "Animal noises:"
+
 
+
for var entry in animalNoises.entrySet():
+
    print "The " + entry.getKey() + " goes '" + entry.getValue() + "'!"
+
 
+
animalNoises["frog"] = "ribbit"
+
animalNoises["cow"] = "moo"
+
 
+
print ""
+
print "New noises:"
+
 
+
for var key in animalNoises.keySet():
+
    print "The " + key + " goes '" + animalNoises[key] + "'!"</nowiki>
+
 
+
At the beginning, you'll notice the map's types may be either explicitly given or omitted-- if the map is empty, the type ''must'' be made explicit, or else the compiler won't be able to deduce it's types and will throw an error.
+
 
+
We then iterate through each entry of the map, and print it to the console.  Next, we add a few entries to the map, then print the whole thing again.
+
 
+
===Functions===
+
A function is essentially a snippet of code that can be defined then executed later (or 'declared' and 'called' later, in programmer jargon).  Functions can accept ''parameters'', which are basically variables you can pass it which can in turn be used by the function itself.  Functions may also optionally ''return'' a variable to the caller.  The CFG for a function declaration is as follows:
+
<nowiki><function_decl> => 'def' IDENTIFIER '(' [<param_list>] ')' ['as' <return_type>] ':' <suite>
+
 
+
<return_type> => <type>
+
 
+
<param_list> => <parameter> [',' <param_list>]
+
 
+
<parameter> => <type> IDENTIFIER
+
 
+
<suite> => <statement_line>
+
          INDENT <statement_list> UNINDENT</nowiki>
+
And here are some example function declarations and calls:
+
<nowiki>def helloWorld():
+
    print "Hello world!"
+
 
+
# Print "Hello world!" to the console
+
helloWorld()
+
 
+
def foo(int a, int b) as int:
+
    return a + b
+
 
+
# Print '5' to the console
+
print foo(2, 3)
+
 
+
# This time, we omit the return type and Spellscript infers it automatically
+
def bar(int x):
+
    return x * 2
+
 
+
# Print '14' to the console
+
print bar(foo(3, 4))</nowiki>
+
 
+
===Classes and Objects===
+
Like many programming languages, Spellscript is ''object-oriented''.  This basically means you can organize your code into ''classes'' (or types), creates ''instances'' of those classes, then pass those instances around just like any other variable.  Special functions called ''methods'' may bound to classes, and called through instances of those classes via the ''dot operator'' ('.').  Variables termed ''fields'' may also be bound to classes, meaning instances of those classes will have those variables bound to them, and may also be accessed via the dot operator.
+
 
+
In Spellscript, classes may also ''inherit'' from other classes and ''override'' their methods (in C++ jargon, all methods in Spellscript are virtual).  Spellscript follows a single-inheritance model, and will feature Java-style multiple-inheritance with ''interfaces'' in the future.
+
 
+
The CFG for a class declaration is as follows:
+
<nowiki><class_decl> => 'class' IDENTIFIER ['(' IDENTIFIER ')'] ':' INDENT ('pass' | <class_body>) UNINDENT
+
 
+
<class_body> => <method_decl> | <field_decl> [NEWLINES <class_body>]</nowiki>
+
 
+
''<field_decl>'' is a special kind of variable declaration, since it must not be an assignment-declaration (this may be supported in the future.)  For now, initial assignments should occur in the ''constructor'', which will be discussed later.
+
 
+
In the case of ''<method_decl>'', 'def' may be replaced with 'redef' if requirement of an overridden method is desired.  Also note that, unlike Python (and as was previously required), there is no 'self' parameter on methods.
+
 
+
Here's an example class declaration, creation, and method call:
+
<nowiki>class MyClass:
+
 
+
    int my_field
+
 
+
    def __init__():
+
        self.my_field = 2
+
 
+
    def someMethod(int i):
+
        return self.my_field * i
+
 
+
MyClass some_class = MyClass()
+
print some_class.someMethod(3)
+
 
+
# prints "6" to the console!</nowiki>
+
 
+
You'll notice two method definitions in the class body: the first is a special method called the ''constructor'', whereas the second is just a regular method accepting one parameter, ''i''.  In the above example, calling "some_class.someMethod(3)" passes "some_class" as the ''self'' parameter and ''3'' as the ''i'' parameter.
+
 
+
The ''constructor'' is declared by specifying a method with name ''__init__'', and is called when the class is first instantiated.  Like any other method, it can accept an arbitrary set of parameters; however, its return type must remain unspecified.  A class instantiation is simply a function call with the class's name as the function name, and a set of parameters passed to it as defined by the constructor-- the newly created class instance is the instantiation's return value.
+
 
+
===Function Pointers===
+
Function pointers' syntax is highly subject to change at the time of this writing, so if things stop working (or there's any general doubt), consult this section of the wiki for the current syntax.
+
 
+
The type of a function pointer is declared as follows: ''<param_type> '$' '(' [<param_type> {',' <param_type>}] ')' ''
+
 
+
Meanwhile, a function pointer is ''referenced'' as follows: '' 'IDENTIFIER '$' '(' [<param> {',' <param>}] ')' '', where ''<param>'' is '' '$'<param_type> | <expression>''
+
 
+
Here's some code to demonstrate valid usage:
+
<nowiki>def globalFunc(int i, int j):
+
  return i + j
+
 
+
def execute(int$(int, int) func, int a, int b):
+
  return func(a, b)
+
 
+
print execute(globalFunc($int, $int), 4, 5)</nowiki>
+
 
+
Function pointers can also have variables bound to them, resulting in a function pointer with the newly bound parameters removed.  Consider the following:
+
 
+
<nowiki>def globalFunc(int i, int j):
+
  return i + j
+
 
+
int$(int) ptr = globalFunc$($int, 3)
+
print ptr(4)
+
# 7 should be printed to the console</nowiki>
+
 
+
This is implicitly what happens when a function pointer is referenced from a class instance (i.e. the instance is bound to the function pointer as the hidden ''self'' parameter).
+

Revision as of 18:06, 5 August 2014

The Spellscript IDE displaying the contents of maze.ss
Depth-first-search mazes generated with maze.ss
A sphere generated with sphere.ss and fill.ss

Spellscript is a programming language for Minecraft, introduced in Hack/Mine v0.6.4 and planned for eventual status as an independent Minecraft mod. Spellscript allows you to mod Minecraft while in-game, no recompilation required. In other words, there's no installation hassle, no fiddling with jars, etc-- just writing or copy/pasting code into an in-game text editor, and you're done. Minecraft objects are represented in Spellscript as first-class objects, just like they are in Java, and you're given complete access to JVM libraries, allowing for all sorts of amazing possibilities...

Spellscript is still in the early development stages, so:

  • Note that the amount of things that can be scripted or modified is still growing (see the section below for currently available hooks)
  • Be prepared to report bugs!

UG7H0j I truly appreciate this blog article. Keep writing.

Z9I8oj I am so grateful for your blog post.Much thanks again. Really Cool.

xPjeS0 Muchos Gracias for your article. Want more.

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox