aboutsummaryrefslogtreecommitdiff
path: root/notes/slul2.txt
blob: c59f8679a9ea6ee7471efcccf856084063e9ebca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

Module header syntax
--------------------


Function syntax
---------------

Constructor/OO syntax
------------------

No constructor should mean that there is a default constructor where all
uninitialized fields are assigned from a corresponding parameter.

    int x
    int y = 123

    constructor
        int x
    code
        this.x = x
    end

How to denote exported vs module-private vs class-private fields?
How to denote exported vs module-private vs class-private functions?
Should there be "friend classes"? Or "friend namespaces"?
    - Should those allow access from other modules? Probably not?

For record-style classes, it would be nice if it was enough to specify
only the fields, and then have a usable struct.


Type syntax
-----------

Integer types:
- Should they be bit-sized? E.g. int32
- Should there be unsigned integers? E.g. uint/uint32
- Should they be range-limited? E.g. int from 0 to 9, int<0..9>, number from 0 to 9

Maybe "neutral" types like int and string should be avoided,
and more specific types should be defined? At the defintiion, it could be
possible to specify a range, maxlength or allowed characters.

Data syntax
-----------

Should the type specification syntax be:

* `int i`
* `i: int`
* `i` i.e. inferred from name
* `const i` i.e. inferred from name, but with explicit definition
    - or e.g. `def i`, `let i`
* `num i`, `string s`, `ref o`, i.e. distinguishing between "major" types
  but inferring more precise types.
* Allow type inferrence in `given` lines.

As field in arena / At ...?
- Should this be possible?
- Where to put the definitions? A specific place per module?
- Or should there be some kind of dynamic mechanism?
  (but then there could be a risk of name conflicts, unless
   each module has it's own namespace)

As field in object / At file-level:

    # Final (only assignable in constructors, must be assigned)
    int x
    # function-specific instance variable
    var int y, only in (f,g)
    # Function-specific, but must be assigned in constructors
    int x2, only in f

As local variable in function / At function-level:

    func f
    code
        # name only
        i = 123
        # type+name
        int i = 123

        # XXX this is probably a bad idea. it hides state!
        #instancevar state 
    end


Statement syntax
----------------



Expression syntax
-----------------

Function calls. Only allowed at the beginning of statements, or at the
beginning of grouping parentheses:

    f
    f arg1
    f arg1, arg2

Should there be methods?
Or should there be per-type identifier scopes?
- That could mean that two places would have to be searched!
- Unless "functions" and "procuderes" are different things
  But that would mean different function naming:
    add_number (from_thing t)
  Instead of:
    add_number (get_number t)
  I think there are pros and cons. In some cases one syntax is more readable
  than the other.

    obj.f arg1
    obj.f arg1

The advantage of this is that getters can be called in subexpressions
without using parentheses:

    obj.f  obj2.get_x, obj2.get_y

How should variables/fields(/properties?) be assigned?

    obj.value = 123
    obj.value! = 123
    obj!.value = 123
    set obj.value, 123
    set obj.value!, 123
    set obj!.value, 123
    # with getters/setters only:
    obj.set_value  123
    obj!.set_value  123

There are actually only two types of expressions that can appear at the
statement level:

- function calls
- assignment expressions

Of course, the left-hand side is a subexpression and could be some deep
multi-level expressionm e.g. obj.arr[obj.index][2].field

Instance variable access and member function calls
--------------------------------------------------

How to distinguish between instance-variables/locals/parameters/constants?

In some languages that might be:

    this.instancevar
    localvar
    parameter
    CONSTANT

Is there a way to make this/local use different syntax but still avoid the
"this." thing?

- "with" statement like in Pascal:
  (but it also leads to ambuigities)
    with this
        instancevar = 123
    end
- With sigills/prefixes
    .instancevar = 123
    %instancevar = 123
    m_instancevar = 123
    .memberfunc 123
    %memberfunc 123
    memberfunc 123  # maybe all calls without an object should be this-calls?
- With implicitly defined setters
    this.set_instancevar 123
    set_instancevar 123    # with implicit this-calls when there's no object
- With some keyword:
    our instancevar = 123
    # related: typescopes
    our bgcolor = fromtarget green
    our bgcolor = fromtype green

Main block
----------

Make simply programs easy to create:
(the main file could inherit from SlulApp, and that could have functions
such as `writeln`)

    main
        writeln "Hello"
    end

(Disallow defining a constructor in this case?)

File structure
--------------

One-class-per-file can lead to lots of tiny files.
Also, what identifier to use for files in subdirectories?

But it is still really nice to be able to see which file a class is defined in
by just looking at it's name.

Maybe an implicit namespace is a good idea?
And there could be some keyword for nested classes.

If files have implicit namespaces, how to define instance fields and 
constructors?

    # should there be interfaces?
    implements Printable

    # must come before any functions or it gives a warning?
    int number
    bool flag

    # Should overloading be allowed?
    # Should constructors have a name or not?
    constructor new
        int number
        bool flag
    code
        ...
    end

    func do_stuff
    ...

Extensability / API / Object Orientation
----------------------------------------

In most languages, interface types (for dynamic function calls) are
implemented some some pointer (e.g. to some list of vtables).

To make it possible to turn an "interface-less" class into a class
with intefaces, without breaking ABI, it would be necessary to put
the vtable separately.

That could be done by passing the vtable in a "fat pointer" when passing
around a reference to the object. I think that is how Go does it?

Another (probably worse?) method could be to store the vtable pointer
in an out-of-band structure, such as a ptr_address-to-vtable map in some
thread-local area?

Or, if arena-chunks are used (it might be better to use thread-local storage
for arena allocation), then the arena chunk header could have such a tree.
Or, it could be a list where each entry has a range (or bitmask) of
addresses that it applies to (this would make objects allocated in a loop
use only one vtable). A downside with this is that the vtables remain
allocated even when not in use.

A completely different approach to OO-interfaces
------------------------------------------------

Have functions return a "state" when the OO-interace should be called.
But this would have to be a deep return (like from a co-routine) if
the OO-interface is nested inside an object in a parameter.

Can the need for "OO-interfaces" be completely eliminated?

* For "global" interfaces, such as for logging, the function pointer
  could go into either the arena or in thread-local-storage.

* For builtin types such as lists, there could be some sentinel value in
  the "header" of the data structure that indicates whether to use
  indirection via a vtable (e.g. for custom lists/maps).
  But it still needs to be compatible with objects that implement
  both a builtin interface as well as a non-builtin interface (or
  multiple builtin interfaces).

Custom statements
-----------------

It would be really nice to be able to create custom control structures etc.
Problems:

* How to distinguish them in the tokenizer?
    - it would actually have been "trivial" (exept for the "else" case)
      if functions had ().
* How to handle following blocks, e.g. "else", "except", "finally", etc.?
* How to implement it internally in an efficient way?
    1. lambdas
    2. an internal loop with switch-case of a return value, e.g.
        int rv;
        CustomCtl state;
        while (rv = custom_ctl_stmt(&state)) {
            switch (rv) {
            case 1:
                ...
                break;
            case 2:
                ...
                break;
        }
    3. an internal loop, where the return value is a series of states?
       (which has the potential to decrease the number of function calls, but
        but on the other hand increases complexity and might increase the
        number memory access,