Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Classes proposal #37

Open
x87 opened this issue Jul 24, 2020 · 7 comments
Open

Custom Classes proposal #37

x87 opened this issue Jul 24, 2020 · 7 comments

Comments

@x87
Copy link
Collaborator

x87 commented Jul 24, 2020

Class Syntax

Here is the proposal to add a new syntax for defining custom classes in scripts. They may eventually become a replacement for static classes in the classes.db.

Let's take a look at an example:

class Actor
  putInCar(car)
     036A: put_actor self in_car car
  end
end

var
  0@: Actor
end

[email protected]($car)

Here we defined a class named Actor that has one method putInCar in. putInCar method has one parameter car. Then we declared a variable 0@ of the type Actor and finally called method putInCar on that class instance.

Important Notes

  • class declaration is lazy. it means no code is produced until it gets actually used in the code. if we were to remove [email protected]($car) no code was produced in this script.
  • each method receives an implicit variable self that represents the class instance the method is called on. in this case it is the variable 0@
  • in this example [email protected]($car) gets compiled into 036A: put_actor 0@ in_car $car

Design Choices

  • when there is a method call statement the compiler takes the method body and replaces the statement with the entire body. it means the method could have many instructions and possibly even labels and they will be put in place of the call statement. in this sense class method are more templates than functions as in the other languages.

  • this however does not prevent local variables from being altered inside the method body. it means there is no separate scope created when the class method is called and if you, for example, change the variable 0@ in the class method it will be a side-effect for the caller:

class Actor
  putInCar(car)
     036A: put_actor self in_car car
     0@ = 2
  end
end

0@ = 1
[email protected]($car)
// 0@ is now equal to 2

There are a few possible choices to this dilemma:

  1. leave it as is, considering this a liberate language design decision (when you invoke subroutines with gosub they could alter local scope as well)
  2. forbid using local variables syntax inside methods. only use arguments passed into the method. this could be inconvenient if you need to keep a local state.
  3. add extra prologue and epilogue before and after each method call that will store and restore all local variables. this adds extra overhead in runtime
  4. use CLEO's scm_function that basically does # 3. this adds unnecessary dependency on CLEO.
    • however if preserving a local scope is important, one could consider using a scm function inside a method body:
class Actor
  putInCar(car)
     0AB1: call_scm_func @local 0

     :local
     036A: put_actor self in_car car
     0@ = 2
     0AB2: ret 0
  end
end

0@ = 1
[email protected]($car)
// 0@ is still 1
@x87
Copy link
Collaborator Author

x87 commented Jul 24, 2020

Conditional Methods

method could be conditional:

class Actor
  inAnyCar()
     00DF:   actor self in_any_car 
  end
end
var
  0@: Actor
end
if
  [email protected]()
then

gets compiled as

if
   00DF:   actor 0@ in_any_car
then

Design Choices

If the method is a template that simply gets inserted in-place of a call statement (see Design choices in the first post) you can not put anything except conditional opcodes in a method to be able to use it in the conditional expression. i.e. this would not be allowed:

class Math
  isZero(x)
     if
       x == 0
     then
        0485:  return_true
     else
        059A:  return_false
     end
  end
end

if
  Math.isZero(1) // false
then

@x87
Copy link
Collaborator Author

x87 commented Jul 24, 2020

Returning Results

It's unclear how to work with returning a value from the methods, i.e consider:

class Actor
  pos()
     00A0: store_actor self position_to x y z
  end

how to represent x, y, z in the code?

Design Choices

  1. pass variables into the method as parameters
  pos(x, y, z)
     00A0: store_actor self position_to x y z
  end
[email protected](1@, 2@, 3@) // 00A0: store_actor 0@ position_to 1@ 2@ 3@

always having to pass a variable to store a result into can be tedious for simple cases like:

  sqr(x, result)
     0A90: result  = x * x
  end

one would want to simply have

class Math
  sqr(x)
     0A90: result  = x * x
  end
end

0@ = Math.sqr(2)
  1. add an implicit variable result then will represent the left side of the call expression:
  pos()
     00A0: store_actor self position_to result[0] result[1] result[2] // variant 1
     00A0: store_actor self position_to result.0 result.1 result.2 // variant 2
  end

then

( 1@, 2@, 3@ ) = [email protected]() // 00A0: store_actor 0@ position_to 1@ 2@ 3@

this adds a lot of unusual syntax into the language.

  1. combine both approaches, i.e. have an implicit result for a single value and pass variables into the method for multiple values:
class Math
  sqr(x)
     0A90: result  = x * x
  end

   pi(int, frac)
      int = 3
      frac = .14
   end
end

0@ = Math.sqr(2) // 0@ = 4
Math.pi(1@, 2@) // 1@ = 3, 2@ = .14

@x87 x87 pinned this issue Jul 24, 2020
@wmysterio
Copy link

wmysterio commented Jul 25, 2020

Powerful! The question is: will the compiler pick up the declared classes if they are described in another file and were included via the {$i} directive. There are plans to write such classes (I already have everything ready for this) and I would like to save the developer from copying everything every time.

I also wanted to ask: will it be possible to add constants to a class? Like this

class Draw
    const MaxWidth = 640.0
end
//
0@ > Draw.MaxWidth

@x87
Copy link
Collaborator Author

x87 commented Aug 21, 2020

@wmysterio
yes, the compiler definitely should be able to read class declarations from imported files.

static class variables could be a nice feature! Will keep that in mind.

@x87 x87 unpinned this issue Dec 5, 2020
@x87
Copy link
Collaborator Author

x87 commented Dec 23, 2020

Class instances should support inline declaration similar to primitive types:

int x = 0
Player p = Player.Create(x, 10.0, 20.0, -100.0)

@x87
Copy link
Collaborator Author

x87 commented Sep 18, 2023

todo: update this proposal after #263

@x87
Copy link
Collaborator Author

x87 commented Sep 6, 2024

2024 Draft


class Car
    /// Directly mapped to command CREATE_CAR
    /// Function and Command signatures must match
    Create<0x00A5>(modelId: int, x: float, y: float, z: float): Car

    /// Update is an instance method
    Update(self: Car, flag: int)
        // update
    end

    /// Can return logical values
    Check(self: Car): logical
        return true
    end
end

Car c = Car.Create(#INFERNUS, 0, 0, 0) // compiled as 00A5
if c.Check() // compiled as a scm function
then
    c.Update(1) // compiled as a scm function
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants