Adding a File Type
Supporting a specific file type at the language level, e.g. adding syntax to easily manipulate an object notation or for serialisation.
Library developers are encouraged to add support for specific file-types at the language level.
An example of this is through the Skript config .csk file type.

Creating a Special Matcher

In order to support the typical path/to/file.ext structure, we cannot use the normal pattern system.
In the constructor, we can specify a dummy pattern. This will not be used directly by the matcher.
1
public MyFile() {
2
super(LIBRARY, StandardElements.EXPRESSION, "path/to/file.ext");
3
}
Copied!
Currently, this would match only the exact text path/to/file.ext but we need it to match any (valid) file-path ending in our .ext file extension.
By overriding the match method we can handle this behaviour ourselves.
1
@Override
2
public Pattern.Match match(String thing, Context context) {
3
if (!thing.endsWith(".ext")) return null; // null = no match
4
if (thing.contains(" ")) { // no spaces in file-path, make sure we're not matching too much
5
context.getError().addHint(this, "File paths should not contain spaces.");
6
// this hint will let people know why it didn't match, IF no other syntax matched
7
return null; // null = no match
8
}
9
return new Pattern.Match(Pattern.fakeMatcher(thing), thing);
10
// the "fake matcher" creates an exact pattern for what the user entered
11
}
Copied!
This can be broken down into three key sections.
  1. 1.
    We make sure the text is what we want (ends in our .ext, is a valid file path, etc.)
  2. 2.
    If it's wrong, we make sure the matcher fails.
  3. 3.
    If it's right, we return a fake "success" matcher.

Handling the File

Since our pattern is non-standard we will need to handle the call manually.
This is done by overriding the compile method.
1
@Override
2
public void compile(Context context, Pattern.Match match) throws Throwable {
3
}
Copied!
The file-path will not be on the stack since it is not an input. Fortunately, it is available to add easily.
1
@Override
2
public void compile(Context context, Pattern.Match match) throws Throwable {
3
final MethodBuilder method = context.getMethod();
4
// method = the current method we're writing
5
final String path = match.<String>meta().trim();
6
// path = the actual syntax path the user wrote
7
method.writeCode(WriteInstruction.push(path));
8
// we 'push' the `path` string onto the stack
9
}
Copied!
So far, we have put only a string onto the stack, but we will likely want either a File object or some malleable handler object for the file that a user can edit.
Imagine, for example, this object is accessible via a MyFile.get(String) static method.
Having put our path string onto the stack we can now call this method directly, which will return the file object from our expression.
1
@Override
2
public void compile(Context context, Pattern.Match match) throws Throwable {
3
final String path = match.<String>meta().trim();
4
final MethodBuilder method = context.getMethod();
5
method.writeCode(WriteInstruction.push(path));
6
7
final Method target = MyFile.class.getMethod("get", String.class);
8
// we get the reflection handle for the method
9
method.writeCode(WriteInstruction.invoke(target));
10
// we tell the compiler to call that method
11
}
Copied!