SourcePawn Transitional Syntax

From AlliedModders Wiki
Revision as of 19:45, 18 June 2014 by BAILOPAN (talk | contribs) (Created page with "__FORCETOC__ <b>EXPERIMENTAL</b>: This document is a prototype and not yet complete. Long term, we would like to provide our users a more modern language. Pawn is showing its...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

EXPERIMENTAL: This document is a prototype and not yet complete.

Long term, we would like to provide our users a more modern language. Pawn is showing its age, and users find manual memory management, buffers, tags, and lack of object-oriented API very frustrating. We can't provide it all at once, but we can begin to take steps in a positive direction.

SourceMod 1.7 introduces what we are calling the "Transitional API". It is built on SourcePawn's "Transitional Syntax," which is a set of language tools to make Pawn feel more modern, and to allow transitioning older APIs without breaking compatibility. Our goal is that, if and when SourcePawn can become a full-fledged modern language, the transitional API will making porting efforts very minimal.

The transitional API, so far, has the following major features and changes:

* Methodmaps - Object-oriented wrappers around existing API.
* Classes - Full object-oriented API.

Methodmaps and classes are very similar, and to the user will look almost identical. The major difference is that methodmaps are an extension of the existing API and tag system. Classes actually introduce new types that have strict behavior. All new APIs will be introduced as classes.

Methodmaps

Introduction

Methodmaps are simple: they attach methods onto a tag. For example, here is our legacy API for Handles:

native CloneHandle(Handle:handle);
native CloseHandle(Handle:handle);

This is a good example our "C-like" API. Using it generally looks something like:

new Handle:array = CreateAdtArray();
PushArrayCell(array, 4);
CloseHandle(array);

A Methodmap can attach functions to the Handle tag, like this:

methodmap Handle {
    public Clone = CloneHandle;
    public Close = CloseHandle;
};

Now, our earlier array code can start to look object-oriented:

new Handle:array = CreateAdtArray();
PushArrayCell(array, 4);
array.Close();

Inheritance

The Handle system has a "weak" hierarchy. All handles can be passed to CloseHandle, but only AdtArray handles can be passed to functions like PushArrayCell. This hierarchy is not enforced via tags (unfortunately), but instead by run-time checks. Methodmaps allow us to make individual handle types object-oriented, while also moving type-checks into the compiler.

For example, here is a transitional API for arrays:

native AdtArray:CreateAdtArray();
 
methodmap AdtArray < Handle {
    public PushCell = PushArrayCell;
};

Note that CreateAdtArray now returns AdtArray instead of Handle. Normally that would break older code, but since AdtArray inherits from Handle, there is a special rule in the type system that allows coercing an AdtArray to a Handle (but not vice versa).

Now, the API looks much more object-oriented:

new AdtArray:array = CreateAdtArray();
array.PushCell(4);
array.Close();

Inline Methods

Methodmaps can declare inline methods and accessors. Inline methods can be either natives or Pawn functions. For example:

methodmap AdtArray {
    public native PushCell(value);
};

This example requires that an "AdtArray.PushCell" native exists somewhere in SourceMod. It has a magic initial parameter called "this", so the signature will look something like:

native AdtArray.PushCell(AdtArray:this, value);

(Of course, this exact signature will not appear in an include file - it's the signature that the C++ implementation should expect, however.)

It's also possible to define new functions without a native:

methodmap AdtArray {
    public native PushCell(value);
 
    public PushCells(list[], count) {
        for (new i = 0; i < count; i++) {
            this.PushCell(i);
        }
    }
};
<pawn>
 
Lastly, we can also define accessors. Currently only "get" accessors are available. For example,
<pawn>
methodmap AdtArray {
    property Size {
        public get = GetArraySize;
    }
    property bool:Empty {
        public get() {
            return this.Size == 0;
        }
    }
    property Capacity {
        public native get;
    }
};

The first accessor simply assigns an existing function as an accessor for "Size". The second accessor is an inline method with an implicit "this" parameter. The third accessor will bind to a native with the following name and signature:

native AdtArray.Capacity.get(AdtArray:this);

Custom Tags

Methodmaps don't have to be used with Handles. It is possible to define custom methodmaps on new or existing tags. For example:

methodmap AdminId {
    public Rights() {
        return GetAdminFlags(this);
    }
};

Now, for example, it is possible to do:

GetPlayerAdmin(id).Rights()

Constructors and Destructors

Methodmaps can also define constructors and destructors, which is useful if they are intended to behave like actual objects. For example,

methodmap AdtArray {
    public AdtArray(blocksize = 1);
    public ~AdtArray();
    public PushCell(value);
};

Now AdtArrays can be used in a fully object-oriented style:

new AdtArray:array = AdtArray();
array.PushCell(10);
array.PushCell(20);
array.PushCell(30);
delete array;

Caveats

There are a few caveats to methodmaps:

  1. When using existing natives, the first parameter of the native must coerce to the tag of the methodmap. Tag mismatches of the "this" parameter will result in an error. Not a warning!
  2. Methodmaps can only be defined on tags. Pawn has some ways of creating actual types (like via struct or class). Methodmaps cannot be created on those types.
  3. Methodmaps do not have strong typing. For example, it is still possible to perform "illegal" casts like Float:CreateAdtArray(). This is necessary for backwards compatibility, so methodmap values can flow into natives like PrintToServer or CreateTimer.
  4. It is not possible to inherit from anything other than another previously declared methodmap.
  5. Methodmaps can only be defined over scalars - that is, the "this" parameter can never be an array. This means they cannot be used for enum-structs.

Grammar

The grammar for methodmaps is:

visibility ::= "public"

methodmap ::= "methodmap" { methodmap-item* } term
methodmap-item ::=
           visibility "native" label? symbol "(" decl-args ")" term
         | visibility label?  symbol "(" decl-args ")" func-body term
         | "property" label? symbol { property-decl } term
property-func ::= "get"
property-decl ::= visibility property-impl
property-impl ::=
           "native" property-func "(" ")" term
         | property-func "(" ")" func-body term
         | property-func "=" symbol