At the moment, the intent for this project is to add a proper scripting language to [Bim](https://github.com/klange/bim), to which both configuration scripts and syntax highlighting will be ported.
Kuroko, [as its name should imply](https://toarumajutsunoindex.fandom.com/wiki/Shirai_Kuroko), will also be made available in [ToaruOS](https://github.com/klange/toaruos) as a general-purpose user language, and some utilities may end up being written in it.
Kuroko inherits some core features by virtue of following _Crafting Interpreters_, including its basic type system, classes/methods/functions, and the design of its compiler and bytecode VM.
If a default argument value is not provided, the expression assigned to it will be evaluated as if it were at the top of the body of the function, like in Ruby (and _not like in Python_).
Blocks, including function `def` blocks and control flow structures like `if` and `for`, must be indented with spaces to a level greater than the enclosing block.
You may indent blocks to whatever level you desire, so long as ordering remains consistent, though the recommendtation indentation size is 4 spaces.
Tabs are not valid as indentation and will be ignored. It is recommended that you use an editor which provides a clear visual distinction between tabs and spaces, such as [Bim](https://github.com/klange/bim).
```py
if False:
print "Oh no, that was a tab."
# → Oh no, that was a tab.
```
Blocks can also accept a single inline statement:
```py
if True: print "The first rule of Tautology Club is the first rule of Tautology Club."
# → The first rule of Tautology Club is the first rule of Tautology Club.
In a departure from Python, Kuroko has explicit variable declaration and traditional scoping rules. Variables are declared with the `let` keyword and take the value `None` if not defined at declaration time:
Objects and classes in Kuroko work a lot like Python or similar languages in that they have an arbitrary and mutable set of fields, which may be methods or other values.
To create a basic object without methods, the `object` class is provided:
```py
let o = object()
o.foo = "bar"
print o.foo
# → bar
```
To supply methods, define a class:
```py
class Foo():
def printFoo():
print self.foo
let o = Foo()
o.foo = "bar"
o.printFoo()
# → bar
```
The `self` keyword is implicit in all methods and does not need to be supplied in the argument list. You may optionally include it in the method declaration anyway:
```py
class Foo():
def printFoo(self):
print self.foo
let o = Foo()
o.foo = "bar"
o.printFoo()
# → bar
```
When a class is instantiated, if it has an `__init__` method it will be called automatically. `__init__` may take arguments as well.
```py
class Foo():
def __init__(bar):
self.foo = bar
def printFoo(self):
print self.foo
let o = Foo("bar")
o.printFoo()
# → bar
```
Some other special method names include `__get__`, `__set__`, and `__str__`, which will be explained later.
_**Note**: Unlike in Python, most types are not actually instances of classes, though many of the same operations still apply to them._
### Inheritence
Classes may inherit from a single super class:
```py
class Foo():
def __init__():
self.type = "foo"
def printType():
print self.type
class Bar(Foo):
def __init__():
self.type = "bar"
let bar = Bar()
bar.printType()
# → bar
```
Methods can refer to the super class with the `super` keyword:
```py
class Foo():
def __init__():
self.type = "foo"
def printType():
print self.type
class Bar(Foo):
def __init__():
self.type = "bar"
def printType():
super().printType()
print "Also, I enjoy long walks on the beach."
let bar = Bar()
bar.printType()
# → bar
# Also, I enjoy long walks on the beach.
```
You can determine at runtime if an object is an instance of a class, either directly or through its inheretince chain, with the `isinstance` builtin function:
Kuroku supports C-style for loops, while loops, and Python-style iterator for loops.
```py
for i = 1, i <5,i =i+1:
print i
# → 1
# 2
# 3
# 4
```
```py
let i = 36
while i > 1:
i = i / 2
print i
# → 18
# 9
# 4
# 2
# 1
```
```py
let l = list()
l.append(1)
l.append(2)
l.append(3)
for i in l:
print i
# → 1
# 2
# 3
```
### Iterators
The special method `__iter__` should return an iterator. An iterator should be a function which increments an internal state and returns the next value. If there are no values remaining, return the iterator itself.
An example of an iterator is the `range` built-in class, which is defined like this:
```py
class range:
def __init__(self, min, max):
self.min = min
self.max = max
def __iter__(self):
let me = self
def makeIter(ind):
let l = me
let i = ind
def iter():
if i >= l.max:
return iter
let out = i
i = i + 1
return out
return iter
return makeIter(self.min)
```
Objects which have an `__iter__` method can then be used with the `for ... in ...` syntax:
```py
for i in range(1,5):
print i
# → 1
# 2
# 3
# 4
```
### Indexing
Objects with the methods `__get__` and `__set__` can be used with square brackets `[]`:
```py
class Foo:
def __get__(ind):
print "You asked for ind=" + ind
return ind * 5
def __set__(ind, val):
print "You asked to set ind=" + ind + " to " + val
Decorators allow functions and methods to be wrapped.
```py
def decorator(func):
print "I take the function to be decorated as an argument:", func
def wrapper():
print "And I am the wrapper."
func()
print "Returned from wrapped function."
return wrapper
@decorator
def wrappedFunction():
print "Hello, world"
wrappedFunction()
# → I take a function to be decorated as an argument: <function wrappedFunction>
# And I am the wrapper.
# Hello, world
# Returned from wrapped function
```
The resulting function will have the same signature as the original function, so wrappers may take arguments to pass to the wrapped function, or may take their own arguments (or both).
Method wrappers work similarly, though be sure to explicitly provide a name (other than `self`) for the object instance:
```py
def methodDecorator(method):
def methodWrapper(instance, anExtraArgument):
method(instance)
print "I also required this extra argument:", anExtraArgument
return methodWrapper
class Foo():
@methodDecorator
def theMethod():
print "I am a method, so I can obviously access", self
print "And I also didn't take any arguments, but my wrapper did:"
let f = Foo()
f.theMethod("the newly required argument")
# → I am a method, so I can obviously access <instance of Foo at ...>
# And I also didn't take any arguments, but my wrapper did:
# I also required this extra argument: the newly required argument
Decorators are _expressions_, just like in Python, so to make a decorator with arguments create a function that takes those arguments and returns a decorator:
Arguments may be passed to a function by specifying their name instead of using their positional location.
```py
def aFunction(a,b,c):
print a,b,c
aFunction(1,2,3)
aFunction(1,c=3,b=2)
aFunction(b=2,c=3,a=1)
# → 1 2 3
# 1 2 3
# 1 2 3
```
This will be slower in execution than a normal function call, as the interpreter will need to figure out where to place arguments in the requested function by examining it at runtime, but it allows for functions to take many default arguments without forcing the caller to specify the values for everything leading up to one they want to specifically set.
Kuroko's repl provides an interactive environment for executing code and seeing results.
When entering code at the repl, lines ending with colons (`:`) are treated specially - the repl will continue to accept input and automatically insert indentation on a new line. Please note that the repl's understanding of colons is naive: Whitespace or comments after a colon which would normally be accepted by Kuroko's parser will not be understood by the repl - if you want to place a comment after the start of a block statement, be sure that it ends in a colon so you can continue to enter statements.
Pressing backspace when the cursor is preceded by whitespace will delete up to the last column divisible by 4, which should generally delete one level of indentation automatically.
The tab key will also produce spaces when the cursor is at the beginning of the line or preceded entirely with white space.
The repl will display indentation level indicators in preceeding whitespace as a helpful guide.
When a blank line or a line consisting entirely of whitespace is entered, the repl will process the full input.
Code executed in the repl runs in a global scope and reused variable names will overwrite previous definitions, allowing function and class names to be reused.
The repl will display the last value popped from the stack before returning. Note that unlike with the `print` statement, objects printed in this way from the repl will not be converted to strings, so they may display differently.
You may be looking at the code examples and thinking Kuroko looks a _lot_ more like Python than "syntax similar to Python" suggests. Still, there are some differences, and they come in two forms: Intentional differences and unintentional differences.
Unintentional differences likely represent incomplete features. Intentional differences are design decisions specifically meant to differentiate Kuroko from Python and usually are an attempt to improve upon or "fix" perceived mistakes.
Two notable intentional differences thus far are:
- Kuroko's variable scoping requires explicit declarations. This was done because Python's function-level scoping, and particularly how it interacts with globals, is often a thorn in the side of beginner and seasoned programmers alike. It's not so much seen as a mistake as it is something we don't wish to replicate.
- Default arguments to functions are evaluated at call time, not at definition time. How many times have you accidentally assigned an empty list as a default argument, only to be burned by its mutated descendent appearing in further calls? Kuroko doesn't do that - it works more like Ruby.