Spellscript

From Hack/Mine Wiki
(Difference between revisions)
Jump to: navigation, search
m (Functions)
m (Basic Syntax)
Line 34: Line 34:
 
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.
 
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 for Spellscript-- note that "decl" is short for "declaration", and that ''identifiers'' are any string of characters containing only letters, numbers, and underscores, and beginning with a letter:
+
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 beginning with a letter:
 
  <nowiki><script> => <statement_list>
 
  <nowiki><script> => <statement_list>
  

Revision as of 04:37, 9 March 2013

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, 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 modded is still growing (see the section below for currently available hooks)
  • Be prepared to report bugs!

Contents

Important Places

  • For a tutorial on how to use Spellscript, 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 release, 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 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

  • Spellscript-Blocks (placeable from the Creative Inventory)
  • Script-Scheduling (brought up by pressing 'tilde' by default)

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 beginning with a letter:

<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' [<variable>]
               'break' | 'continue'

<variable_decl> => <type> <inner_var_decl>
<inner_var_decl> => IDENTIFIER ['=' <expression>] [',' <inner_var_decl>]

<type> => <basic_type> | (<type> '[' ']') | <func_ptr_type>
<func_ptr_type> => <type> '$' '(' [<type> {',' <type>}] ')'
<basic_type> => 'bool' | 'int' | 'float' | 'double' | 'string' | IDENTIFIER

<assignment> => IDENTIFIER ('=' | '+=' | '-=' | '/=' | '%=') <expression>

expression, control_statement, function_decl, class_decl, will all be defined later.

Type System

Spellscript features classes, strings, lists, and function pointers (which follow a pass-by-reference model), and primitive types (which follow a pass-by-value model). The primitive types are bool, int, 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
string s = "Hello world!"
string t = None
MyClass m = MyClass()
int$(int, float) funcPtr = someFunction

A notable distinction from Python is that variables must be declared before they are used.

Expressions

The best way to describe expressions in a language is 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.

Operations

Operator Description Order
= += -= *= /= %= Assignment, Compound Assignment Right-to-Left
is, is not Identity Tests Right-to-Left
== != Equality Tests Right-to-Left
instanceof, as Type Test, Safe-Cast --
or Boolean OR Left-to-Right
and Boolean AND Left-to-Right
< <= >= > Comparison Right-to-Left
+ - Addition, Subtraction Left-to-Right
* / % Multiplication, Division, Modulo Left-to-Right
not x, -x Inversion, 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>]

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

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 lowest 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 an "inner type", which limits which kinds of objects can be added to them (but also 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 int n in numbers:
    print n

numbers.append(17)

for int i in numbers.size():
    print numbers[i]

for int 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 player's chat display. Next, we append '17' to the list, then print the whole thing again.

Finally, we show a handy way of iterating through some specific numbers, simply by instantiating the list in the for loop's declaration.

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' [<return_type>] IDENTIFIER '(' [<param_list>] ')' ':' <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 int foo(int a, int b):
    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 as assignment-declaration (this may be supported in the future.) Initial assignments should occur in the constructor, which will be discussed later.

<method_decl> is a special kind of function declaration. Firstly, its first parameter must be self, which refers to the class instance itself-- it must not have a <type> preceding it, as it's inferred automatically. Secondly, 'def' may be replaced with 'redef' if requirement of an overridden method is desired.

Here's an example class declaration, creation, and method call:

class MyClass:

    int my_field

    def __init__(self):
        self.my_field = 2

    def someMethod(self, 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>}])

The simplest way to reference a function declaration as a function pointer is to use its name, without parenthesis at the end. However, this will cause a compilation error if the call is ambiguous (suppose the function/method is overloaded)-- thus, to clarify, one can use the following syntax: <function name> ('$' '(' ')' | ['$'] '(' '$' <param_type> {',' '$' <param_type>} ')') Notice how a lack of parameter types requires a '$' to be included before the parenthesis, otherwise it would be ambiguous with a function call. This complexity is part of the reason the syntax may be revised in the future.

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

Personal tools
Namespaces

Variants
Actions
Navigation
Toolbox