Changes to the AGS Scripting language

Started by monkey0506, Thu 03/07/2014 21:58:10

Previous topic - Next topic

Calin Leafshade

A maybe tangential point, but does it make sense to have a "managed struct". Aren't structs generally considered to be value types on the stack?

Maybe renaming "managed struct" to "class" would be wise semantically?

Since we are essentially designing a language here should we have some kind of symposium or somethign and draw up a formal specification so we dont run into absurdities later?

Crimson Wizard

#21
Quote from: Calin Leafshade on Fri 04/07/2014 14:32:55
Since we are essentially designing a language here should we have some kind of symposium or somethign and draw up a formal specification so we dont run into absurdities later?
And I was suggesting this nearly two years ago. :D.


...two years... time's flying :-\

monkey0506

#22
Here's my thoughts on the discussion so far:

* static structs should be allowed where they make sense, like Game, Room, and System.
* The Mouse.x and Mouse.y instance fields should be replaced by static properties, and the struct made static.
* The compiler will also then need a fix for accessing non-static members of a static member, e.g., System.AudioChannels[3].PlayingClip
* The new keyword internal should be added for structs that cannot be instanciated using keyword new
* internal structs should have access to a protected constructor, and should be able to return managed instances of the class by invoking it (e.g., via a static Create method, where appropriate)
* If reasonably possible, every existing "managed struct" should be revised to be an "autoptr managed struct" (this seems to work for user types already!)
* "autoptr managed struct" should be replaced by the new keyword class, a nullable reference type

Expanding from my earlier sample code, this feels much nicer:

Code: ags
function game_start()
{
  DynamicAttribute da1 = DynamicAttribute.Create();
  da1.DisplayInfo();
  DynamicAttribute da2 = DynamicAttribute.Create(42, "3.14", "Hello World!");
  da2.DisplayInfo();
  da1.iProperty = 24;
  da1.fProperty = 4.13;
  da1.sProperty = "!dlroW olleH";
  da1.DisplayInfo();
  DynamicAttribute array[] = CreateDynamicAttributeArray(5);
  array[3].iProperty = 1234;
  array[3].DisplayInfo();
}


I avoided dealing with "pointers" by defining the type as "autoptr managed struct DynamicAttribute" (hence my extension of Calin's suggestion to rename it to "class").

This will break a lot of things in user scripts, but I think it's necessary moving forward to develop the language.

Edit: btw, inb4wut -- I just wanted to point out, before anyone gets all flustered and confused, that AGS already has the autoptr keyword, but the only case where it's used is the String type. I'm not sure if it's fit for general use, which is why I said, "If reasonably possible...". As I said though, it seems to run without causing any problems.

Calin Leafshade

#23
I think I agree with everything monkey says except maybe the factory methods.

I think the factory methods were included as a stop gap because there was no new keyword.

So where we have:

Code: ags
DynamicSprite *d = DynamicSprite.Create(320,180);


This should be replaced by

Code: ags
DynamicSprite d = new DynamicSprite(320,180);


Of course the factory method can be left in place for compatibility but it should simply be a wrapper for the new syntax.

Any class marked as internal should simply not be able to be instantiated in the script domain. These would currently be Character, Object, Hotspot, AudioClip, AudioChannel and Region. (might be more)

-----

I think structs should be value types and classes should be ref types and the pointer syntax should simply be dropped if thats possible.


Edit: Thinking about this is fun, i'll draw up some kind of doc soonish with my thoughts en masse.

Crimson Wizard

#24
Quote from: monkey_05_06 on Fri 04/07/2014 16:11:28
* The new keyword internal should be added for structs that cannot be instanciated using keyword new
* internal structs should have access to a protected constructor, and should be able to return managed instances of the class by invoking it (e.g., via a static Create method, where appropriate)
AFAIK in C# (if we take one for analogies) "internal" structs are those that cannot be used by external module. Here we have a struct that may be used externally (in scripts) but cannot be created with "new" by scripts.
Also, as Gurok mentioned above, protected members should be (are?) accessible in extender functions.
Should not constructor be "internal" instead?

Quote from: monkey_05_06 on Fri 04/07/2014 16:11:28
This will break a lot of things in user scripts, but I think it's necessary moving forward to develop the language.
We may add another backwards compatibility option to compiler. It will ignore single "*" sign after "autoptr" type, unless the type is string. Or, something like that.
This is important, because our next update will include other features too, and people will want to have them without having to fix tonns of scripts.

Calin Leafshade

Ive changed my mind about structs being value types. It would be confusing for newbies to pass an object and get a copy of it instead.

So what would the distinction between a struct and a class actually be?

Is a struct just an automatically instantiating class?

Code: ags

struct myStruct {
    int i;
};

class myClass {
    int i;
};

myStruct myStructInstance; // this is instantiated
myClass myClassInstance; // this is a null reference


What is the utility of this distinction? Why would someone, in practice, ever use a struct?

Crimson Wizard

Quote from: Calin Leafshade on Fri 04/07/2014 18:53:53
What is the utility of this distinction? Why would someone, in practice, ever use a struct?
Some newbie scripter who needs a simple way to group variables and don't want to have annoying "null reference" problem ;).
+ backwards compatibility...

SpeechCenter

Quote from: Crimson Wizard on Fri 04/07/2014 18:05:14
Quote from: monkey_05_06 on Fri 04/07/2014 16:11:28
* The new keyword internal should be added for structs that cannot be instanciated using keyword new
* internal structs should have access to a protected constructor, and should be able to return managed instances of the class by invoking it (e.g., via a static Create method, where appropriate)
AFAIK in C# (if we take one for analogies) "internal" structs are those that cannot be used by external module. Here we have a struct that may be used externally (in scripts) but cannot be created with "new" by scripts.
Also, as Gurok mentioned above, protected members should be (are?) accessible in extender functions.
Should not constructor be "internal" instead?
That would be confusing indeed. In C# static classes mean they cannot be instantiated (http://msdn.microsoft.com/en-us/library/79b3xss3.aspx). So either support that or define private/protected constructor.

Snarky

No, just like "abstract sealed", static is wrong because the engine is in fact creating instances of the class (and providing those instances to the user). CW had it right that it's the constructor that should be declared internal; by C# analogy (considering the engine and the user scripts as different assemblies) that would have the desired effect.

Gurok

#29
Hey! Abstract doesn't mean someone can't create an instance for you. It just means you can't directly construct it.

I believe monkey_05_06 was suggesting to use static only for classes it applies to (i.e. those with all-static members). This makes sense, I guess, but I don't think it's 100% necessary.

CW has it right. Internal is to keep a property within the assembly it was declared in. If we had a property that meant "you can't construct this and can't use the keyword extends on it", I would prefer "builtin".

Regarding structs and the point of them v/s managed structs, I think it's actually a good way to ease people into the concept of objects. As CW said, there are no nulls. It's a very safe place to play.
[img]http://7d4iqnx.gif;rWRLUuw.gi

Calin Leafshade

The problem with declaring the constructor internal is that it means all classes require a constructor and allowing a default, empty constructor would be cool.

I think defining the whole class as internal would be better. i.e "this class cannot be instantiated in the script domain"

EDIT AFTER GUROK: On balance defining the whole class as "builtin" is probably better.

Crimson Wizard

#31
BTW, an interesting thing is that currently user can implement any function from builtin classes.
For example, I can write in script:

Code: ags

function Character::ChangeRoom(int room, int x, int y)
{
  Display("bla");
}


Did anyone know that? :)
This works because script implementation overrides builtin one when registered.
Do we need to protect builtin classes from such atrocities?

Calin Leafshade

#32
No, thats *awesome*

Can you override Say and have dialogs use it correctly?

EDIT: Speaking of overrides how difficult would overloading functions be?

EDIT EDIT: (Although we probably need to be able to provide access to the base function if overridden.

Gurok

If functions are implemented in such a way, AFAICT they're allowed but they just get ignored. The engine's implementation still gets called. Are you seeing different behaviour, CW?
[img]http://7d4iqnx.gif;rWRLUuw.gi

Crimson Wizard

#34
Quote from: Calin Leafshade on Fri 04/07/2014 20:27:31
Can you override Say and have dialogs use it correctly?
No, because dialogs do not call "Say" via script API, obviously, they call program functions directly (and THAT is the problem).

Quote from: Gurok on Fri 04/07/2014 20:38:39
If functions are implemented in such a way, AFAICT they're allowed but they just get ignored. The engine's implementation still gets called. Are you seeing different behaviour, CW?
Actually I do.
I typed "player.ChangeRoom(0)", and "bla" was displayed instead of room change.

Gurok

Quote from: Crimson Wizard on Fri 04/07/2014 20:38:58
Quote from: Calin Leafshade on Fri 04/07/2014 20:27:31
Can you override Say and have dialogs use it correctly?
No, because dialogs do not call "Say" via script API, obviously, they call program functions directly.

Quote from: Gurok on Fri 04/07/2014 20:38:39
If functions are implemented in such a way, AFAICT they're allowed but they just get ignored. The engine's implementation still gets called. Are you seeing different behaviour, CW?
Actually I do.
I typed "player.ChangeRoom(0)", and "bla" was displayed instead of room change.

Oh right. I was testing Say, naturally. :D

Code: ags

function Character::Say(const string message, ...)
{
	Display("Here's the message: %s", message);
}
[img]http://7d4iqnx.gif;rWRLUuw.gi

Snarky

Personally I say get rid of structs as non-nullable types, and lose the pointer syntax completely. A bit of work for devs updating scripts now is worth it for the simplification it brings.

Quote from: Gurok on Fri 04/07/2014 20:13:20
Hey! Abstract doesn't mean someone can't create an instance for you. It just means you can't directly construct it.

I believe monkey_05_06 was suggesting to use static only for classes it applies to (i.e. those with all-static members). This makes sense, I guess, but I don't think it's 100% necessary.

CW has it right. Internal is to keep a property within the assembly it was declared in. If we had a property that meant "you can't construct this and can't use the keyword extends on it", I would prefer "builtin".

Regarding structs and the point of them v/s managed structs, I think it's actually a good way to ease people into the concept of objects. As CW said, there are no nulls. It's a very safe place to play.

Gurok, I was responding to SpeechCenter, who suggested using "static" as the "builtin" keyword. As for abstract, we already discussed this upthread.

Using "internal" on the constructor to make it uninstantiable by user code would be consistent with C#. Contra Calin, I'm not sure I see the problem with requiring the former managed classes to declared an explicit "internal" constructor, and I think it might be useful to have a keyword that could be used to make specific other methods engine-only as well, but if you prefer to tag the class rather than the constructor I agree something like "builtin" might be better.

Overriding methods is hella cool (and sort of obsoletes extender functions), but there's no super() yet, is there? That's probably necessary to make it super-useful. (laugh)

Crimson Wizard

#37
Quote from: Snarky on Fri 04/07/2014 20:44:58
Overriding methods is hella cool (and sort of obsoletes extender functions), but there's no super() yet, is there? That's probably necessary to make it super-useful. (laugh)
That was not overriding in the sense of type hierarchy, but in the sense of... I dunno, substitution (like with delegates?).
Therefore word "super" won't help here, because engine does not provide parent class for Character, but its implementation. We would need word... "internal"? Like "internal::ChangeRoom", unless that's too weird.

Overriding in the sense of polymorphism was already supported with extending structs (to some degree): http://www.adventuregamestudio.co.uk/wiki/Extender_Methods_mean_Polymorphism!#Polymorphism

Snarky

Hmmm... you mean that the override won't just affect instances of your class, but all instances of the original class throughout the code? Yes, that is a bit different from how inheritance override usually works. I'm starting to see what you mean about planning out the language design, because this is starting to sound a little kludgey.

I think you could still argue that conceptually super() (or base(), going off C#) is an easier, more familiar term, even if it works slightly different from the Java way. But keep going too far down that path of "close enough", and the whole thing will be an even stranger, more non-standard mess than it already is.

Crimson Wizard

Not every hidden feature of AGS script is worth to be kept and developed further, some work simply because it was coded this way. While it may be fun to uncover them, their usefullness may be dubious.

I am all for moving towards tidy language.
The backwards compatibility is not a biggest issue, because in the worst case we may make more compiler switches in addition to the ones we already have (non-OO functions, etc).

As for keywords and concepts, I believe we first need to have a list of which entities, technically, we have/want to have, because this list defines which keywords we need to use and control them.

1. Storage / scope.
We have following places to store objects
* global memory: exists until program exit
* local memory (stack): exists until function returns
* managed memory: exists until reference count reaches zero

2. Implementation.
* implemented in separate assembly (e.g. engine)
* implemented in this assembly

3. Place in hierarchy
* can be inherited
* cannot be inherited

4. Access to members.
etc.

SMF spam blocked by CleanTalk