aboutsummaryrefslogtreecommitdiff
path: root/notes/interface_files_deps.txt
blob: 3f6a7a0be60b5714cb80284ffb5d27d903c14a65 (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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317

Interface dependencies in interface files?
==========================================

TODO: Clean this up.

Should dependencies have to go into the interface files?

Yes: In order to keep interface files as single files, they would have
to go into the interface files (e.g. as `use` lines or something similar).

But that leads to another problem: Isn't it desireable for consistency to have
the same method to include files for both the interface and the implementation?
Also, it important that users don't confuse interface dependencies with
implementation dependencies.

Can these two problems be solved at the same time?

The best solution so far appears to be this syntax, i.e. API-hashes in
a separate lockfile and versions as blocks that can contain a comment
about how the API has changed in that version.

    ```
    version 0.1.0
    # Initial version
        use_types some_module 1.0
    end
    version 0.1.1
    # Adds new enum values for x
    end
    version 0.1.2
    # Adds support for usage together with y in some_module
        use_types some_module 1.3
    end
    ```

Maybe use some other keyword than `version`, to clarify that it is not about
implementation versions?

- `api`
- `interface`  (best?)
- `since` (sounds strange in case of the first version)


Have `use` lines for interface and `dependencies.index` for implementation
--------------------------------------------------------------------------

* This is inconsistent obviously.
* Could have a different keyword for interface dependendencies.
    - Perhaps `interface_depends` like old SLUL?
    - Or something without an underscore:
        - `interfacedepends ...`
        - `interface depends ...`
        - `with type <type> from ...`
        - `with type <module>.<type> ...`
        - `with Type from ...` (good. but long:)
        - `with Type from <module> <version> since <sincever> <hash>`
        - `use type <type> from ...`
        - `using types from ...`
        - `external <type> from ...`
        - `here <type> = <module> <version>`
        - `expose <type> from <module> <version>`
        - `export <type> from <module> <version>`
        - `export <module> <version>`
        - `expect <module> >= <version>`
        - `extract <type> from <module> <version>`
        - `require depends <module> <version>`
        - `mustdepend <module> <version> since <sinceversion>`
           (a bit misleading if deps can be minimized)
        - `musthave ...`
        - `transitive <module> <version> since <sinceversion>`
        - `mutual dep ...`
        - `shared dep ...`
        - or some abbreviated word: `idep`, `mdep`, `sdep`
    - Also, perhaps the API hash should be included. And it should go first
      for alignment:
        - `expose <hash> <module> <version> since <sinceversion>`
        - `typesfrom <hash> <module> <version> since <sinceversion>`
           (good, but long)
        - `use ...`
        - or use a multi-line format (but with 43-character API-hashes,
          there's not a lot of space left. Even with base94 it would be
          40 characters).
        - if the API-hash can be reduced to 160 bits, then 27 characters is
          enough (`log(64**27,2)==162`). With a short keyword like `use`,
          it should be possible to fit within 80 characters wide lines.
        - maybe it can be shortened further... see below.
* How to tokenize the version? (it's not a float)
    - in old SLUL, the version appears in the module header,
      so it had a separate tokenizer anyway.
    - having the parser "tell" the tokenizer whether floats or versions
      are expected can work, but is a bit ugly.
    - combine/prefix with `:`? e.g. `somemodule:1.0 since :0.3`
    - or tokenize together with the preceeding keyword
      (e.g. `version 1.2.3` or `since 1.2.3`)
    - if using API-hashes, then the name and version are just for the user
      (and to check that the user is not being tricked). So they could go
      into double quotes, as a string:
        ```
        interfacedepends AAAAAAAAAAAA "longmodulename 123.456.789" since 1.2
        ```
* How to tokenize the API-hash?
* include the API hash in versions?
    - It's important to use the right module.
    - And the interface.slul file should be freestanding.
    - BUT the compiler will do API checks at compile-time, so there's no
      security reason to have a long hash here. Maybe it could be really short,
      like 12 characters? `log(64**12,2)==72`
        - the package registry could enforce that no API-version of any module
          has a the same hash as any other version (whether in the same module
          or not).
        - uniqueness could also be enforced at client side at the developer
          even across different registries
        - uniqueness could also be enforced at load time, to prevent loading
          two different API-hashes with the same 72-bit prefix.
        - security-critical libraries could choose to use the full 43-character
          hash (without a trailing `=`).
    - This can go wrong if the API-hash is absent in the interface file:
        - The user might compile with an incorrect dependency, and end up with
          a different API-hash (and this would affect API-hashes of dependent
          modules), leading to a confusing error when trying to load some
          application using the library.
        - A library author might decide the change to an incompatible
          dependency with the exact same name and version. This change would
          ONLY go into the dependencies.index file. So it would break API
          without any changes at all to the interface file!
          (but shouldn't API hashes of the since-versions go into the
          interface file? or some kind of API-lockfile?)
    - And regardless, during development, one could allow the hash to be absent:
        ```
        interfacedepends "longmodulename 123.456.789"
        ```
    - Alternative solution: Put API-hashes in a separate API-lockfile.
      And the API-hashes can include API-hashes of interface dependencies as
      well.
* Several components are needed:
    - Module to depend on
    - And a mapping from since versions to dependency versions:
        - Module version to depend on
        - Since version
    - The mapping could be autodetected, but I think it is better to have
      explicit versions that can serve as documentation.
        - Maybe the <sinceversion> could be autodected, though?
        - Also, it is probably a good idea to auto-minimize interface
          dependencies.
        - Existing symbols should have API-hashes of some kind (either per
          symbol or per API-version), and those should include the interface
          dependencies and their versions:
            - So modification is prevented.
            - So safe autodetection is possible.
            - But error messages will not be as helpful
            - But it might not be as intuitive?
    - Could have repeated interface_depends lines, like old SLUL:
        ```
        interface_depends <module> <depversion1> since <sinceversion1>
        interface_depends <module> <depversion2> since <sinceversion2>
        ```
    - Or could have a list/block with multiple versions:
        ```
        interface_depends <module>
            <depversion1> since <sinceversion1>
            <depversion2> since <sinceversion2>
        end
        ```
    - Or could have a single list/block with all modules:
        ```
        interface_depends
            <module> <depversion1> since <sinceversion1>
            <module> <depversion2> since <sinceversion2>
        end
        ```
    - Or could have a depends line per symbol:
        ```
        since <sinceversion>
        types from <module> <version>
        func do_stuff
            int x
        end
        ```
    - Or depends line + types per symbol:
        ```
        since <sinceversion>
        type <types...> from <module> <version>
        func do_stuff
            int x
        end
        ```
    - Or sections:
        ```

        since <sinceversion>
        types from <module> <version>

        ...
        ```
    - Or sections with types:
        ```

        since <sinceversion>
        type <types...> from <module> <version>

        ...
        ```
    - Or versions with "use_types" lines (best?):
      (I think use_types is more intuitive than add_types)
        ```
        version 0.1.0
        # Initial version
            use_types some_module 1.0
        end
        version 0.1.1
        # Adds new enum values for x
        end
        version 0.1.2
        # Adds support for usage together with y in some_module
            use_types some_module 1.3
        end
        ```
    - Wildcard types (but how should this work with trees? if there is only
      a single lookup per occurence of a type, and searches are only done
      at {underscore, start of uppercase sequence, end}, then it might work?):
        ```
        type Gtk* from gtk3 3.0
        ```

Have a `main.slul` file
-----------------------

* This would contain `use` lines to specify dependencies on other modules.
* In addition, it could contain functions/constants
    - but this might be a bad thing.
    - mixing code and dependencies could lead to merge conflicts.
* In libraries, any `use` lines in `interface.slul` would be implicitly
  includes in the implementation too (as if they were in `main.slul`).


Have an `implementation.slul` file
----------------------------------

* This would only contain `use` lines.
* What should it be called?


Have separate "interface modules"
---------------------------------

* That is apparently what Guile does:
    https://ekaitz.elenq.tech/fasterMes5.html
* It could allow implementing more than one interface in a module.
* It might actually simplify the bootstrap compiler
* But it also means more work when creating a library.
* This is unrelated to whether the interface goes into one or multiple files.


Use sections/markers to combine multiple files into one
-------------------------------------------------------

This is not only useful for library interfaces, it could also be used to
create single-file applications. Basically, it would be structured something
like this:

    MARKER <FILENAME1>
    CONTENTS1
    ....

    MARKER <FILENAME2>
    CONTENTS2

I think there might already be some existing (human-readable) file format for
this, which allows multiple files to be placed in a single file.
* There's of course multipart MIME, but it's not that readable, and it's
  perhaps too flexible/complex? And it also requires MIME-type assignments
  (which can be personal prs.* in the beginning, though)
* Cookie-jar format, but extended with filenames:
    % file1
    % file2

If files "purposes" are based on file extensions, then it could look like
this:

    .deps:

    stdlib 1.0
    somelib 0.5

    .slul:

    class Xyz
    ...

To trigger this multi-file mode, the first non-comment line would have to be
a file marker line.

Actually, this could be generalized to class files also:

    .deps:

    stdlib 1.0
    somelib 0.5

    Xyz.slul:
    ...

Other syntaxes:

    [.deps]
    [Xyz.slul]

    === .deps
    === Xyz.slul

    --- .deps
    --- Xyz.slul

The multi-file syntax should only be allowed for interface files and for
single-file modules (otherwise it could be confusing). For interface files,
the file name could be `interface.slul` (or `<modulename>.slul`). For
single-file modules, the filename could be simply `<modulename>.slul`.