Spellscript

From Hack/Mine Wiki
Revision as of 04:46, 1 September 2014 by 172.251.211.122 (Talk)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
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!

Contents

Important Places

  • For a Spellscript tutorial geared towards beginner programmers, see Spellscript Tutorial.
  • For details on how to use the IDE (i.e. code editor), see Spellscript IDE.
  • For a list of bugs, see Spellscript Bugs (since we're in the initial phases, this is a must read!)
  • For a Sublime Text 2 language package, see here.
  • For a general-purpose language reference, read on...

Spellscript Hooks in Hack/Mine

Hack/Mine outsources a growing amount of its functionality to Spellscript, such that users can thoroughly customize their RPG experience. Currently, all of the spells and item effects are implemented with Spellscript. Also, currently, only admins and ops can create Spellscript scripts! Of course, users can run these scripts, provided they've been setup to do so.

Admins may currently employ Spellscript in the following ways:

In-Game Hooks

External Hooks

  • Command-line Access
  • Files in the ".minecraft/spells" directory

Language Mechanics

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:

<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>

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...

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

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.

var mc = MyClass()
var s = "Hi again!"
var fp = someFunction

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. 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

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))

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:

<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'

And here are some use cases:

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!"

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:

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

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.

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] + "'!"

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:

<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

And here are some example function declarations and calls:

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))

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:

<class_decl> => 'class' IDENTIFIER ['(' IDENTIFIER ')'] ':' INDENT ('pass' | <class_body>) UNINDENT

<class_body> => <method_decl> | <field_decl> [NEWLINES <class_body>]

<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:

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!

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:

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)

Function pointers can also have variables bound to them, resulting in a function pointer with the newly bound parameters removed. Consider the following:

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

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).

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox