Stabel

Check-in [2f5dcec13a]
Login
Overview
Comment:It is now possible to write Stabel programs that return a string.
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2f5dcec13af41df2ead2e6c3a6a0f6c105e48328570d0e2f2fde3491f4b51a87
User & Date: robin.hansen on 2021-09-09 10:46:59
Other Links: manifest | tags
Context
2021-09-09
10:58
Fixed bug where reliance on string module was not inferred when encountering a string literal. check-in: 6fd8ec29cf user: robin.hansen tags: trunk
10:46
It is now possible to write Stabel programs that return a string. check-in: 2f5dcec13a user: robin.hansen tags: trunk
2021-09-06
20:39
String literals. [3fac68d49a]. check-in: fc3b105833 user: robin.hansen tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Modified bin/cli.js from [b53fed3fe0] to [61c21a98f0].

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
160
161
162
163
164
165
166
167
168
169


170
171
172
173
174
175
176
...
185
186
187
188
189
190
191
192





193
194





















                    files: files
                });
                break;
            case "compilationDone":
                if (typeof entryPoint === "undefined") {
                    console.log("Compiled successfully");
                } else {
                    executeWat(msg.wast, entryPoint);
                }
                break;
            case "compilationFailure":
                console.error(msg.error);
                break;
            default:
                console.error("Unknown message received from Elm compiler: ", msg);
................................................................................
* init <package_name>: initialize a new project in the current directory.
* compile: compile the project.
* run <function_name>: compile the project and execute the given function.
* help: print this help message.
    `);
}

async function executeWat(wat, entryPointName) {
    const wabt = await wabtInit();
    const wasmModule = wabt.parseWat('tmp', wat).toBinary({}).buffer;



    const memory = new WebAssembly.Memory({
        initial: 10
    });

    const imports = {
        host: {
................................................................................
        return;
    }

    entryPoint();

    const memView = new Int32Array(memory.buffer);
    // First three i32 elements are stack and heap information
    const returnValue = memView[3].toString(); 





    console.log(returnValue);
}




























|







 







|

|
>
>







 







|
>
>
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
...
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
                    files: files
                });
                break;
            case "compilationDone":
                if (typeof entryPoint === "undefined") {
                    console.log("Compiled successfully");
                } else {
                    executeWat(msg.wast, entryPoint, msg.isStringReturned);
                }
                break;
            case "compilationFailure":
                console.error(msg.error);
                break;
            default:
                console.error("Unknown message received from Elm compiler: ", msg);
................................................................................
* init <package_name>: initialize a new project in the current directory.
* compile: compile the project.
* run <function_name>: compile the project and execute the given function.
* help: print this help message.
    `);
}

async function executeWat(wat, entryPointName, isStringReturned) {
    const wabt = await wabtInit();
    const wasmModule = wabt.parseWat('tmp', wat, {
        bulk_memory: true
    }).toBinary({}).buffer;

    const memory = new WebAssembly.Memory({
        initial: 10
    });

    const imports = {
        host: {
................................................................................
        return;
    }

    entryPoint();

    const memView = new Int32Array(memory.buffer);
    // First three i32 elements are stack and heap information
    const returnValue = memView[3]; 

    if (isStringReturned) {
        const returnStr = fetchStringValue(memView, returnValue);
        console.log(returnStr);
    } else {
        console.log(returnValue);
    }
}

function fetchStringValue(memView, strPointer) {
    // String structure => typeId | array 
    const strOffset = strPointer / 4;

    const arrayPointer = memView[strOffset + 1];
    const arrayOffset = arrayPointer / 4;

    const strLen = memView[arrayOffset];

    const content = [];
    const strValueOffset = arrayOffset + 1;
    for (let i = 0; i < strLen; i++) {
        content.push(memView[strValueOffset + i]);
    }

    const decoder = new TextDecoder();
    const strAsUtf8 = new Uint8Array(content);
    return decoder.decode(strAsUtf8);
}

Modified src/CLI.elm from [790a55203e] to [9ea3e5c648].

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
...
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
...
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
...
228
229
230
231
232
233
234
235
236
237
238
239

240
241
242
243
244
245
246
            let
                updatedModel =
                    PackageLoader.update packageLoaderMsg packageLoaderModel
            in
            case updatedModel of
                PackageLoader.Done qualifiedAst ->
                    case typeCheckAndRun entryPoint qualifiedAst of
                        Ok wast ->
                            ( ( entryPoint, updatedModel, typeErrors_ )
                            , outgoingPort <| encodeCompilationDone wast
                            )

                        Err ( sourceFilesRequired, errors ) ->
                            ( ( entryPoint, updatedModel, errors )
                            , outgoingPort <| encodeReadFilesToReportError sourceFilesRequired
                            )

................................................................................
            in
            ( model
            , outgoingPort <|
                encodeCompilationFailure errorMessages
            )


legalEntryPointType : Type.FunctionType
legalEntryPointType =
    { input = []
    , output = [ Type.Int ]
    }









typeCheckAndRun :
    Maybe String
    -> Qualifier.AST
    -> Result ( List String, List TypeCheckerProblem.Problem ) String
typeCheckAndRun entryPoint qualifiedAst =
    case TypeChecker.run qualifiedAst of
        Err typeErrors ->
            let
                sourceFiles =
                    typeErrors
                        |> List.map TypeCheckerProblem.sourceLocationRef
................................................................................

                entryPointFunction =
                    entryPoint
                        |> Maybe.andThen (\n -> Dict.get n typedAst.functions)
            in
            case entryPointFunction of
                Just fn ->
                    if fn.type_ == legalEntryPointType then






                        typedAst
                            |> Codegen.run exportedFunctions
                            |> Wasm.toString
                            |> Ok

                    else
                        let
                            sourceLoc =
                                fn.sourceLocation
                                    |> Maybe.withDefault SourceLocation.emptyRange
                        in
                        Err
                            ( [ sourceLoc.source ]
                            , [ TypeCheckerProblem.BadEntryPoint
                                    sourceLoc
                                    fn.name
                                    legalEntryPointType
                                    fn.type_
                              ]
                            )

                Nothing ->
                    typedAst
                        |> Codegen.run exportedFunctions
                        |> Wasm.toString
                        |> Ok



-- Json Encoding/Decoding


encodeSideEffectAsJson : PackageLoader.SideEffect -> Json.Value
................................................................................
            Encode.object
                [ ( "type", Encode.string "resolvePackageModules" )
                , ( "package", Encode.string moduleName )
                , ( "path", Encode.string path )
                ]


encodeCompilationDone : String -> Json.Value
encodeCompilationDone wast =
    Encode.object
        [ ( "type", Encode.string "compilationDone" )
        , ( "wast", Encode.string wast )

        ]


encodeCompilationFailure : String -> Json.Value
encodeCompilationFailure errorMsg =
    Encode.object
        [ ( "type", Encode.string "compilationFailure" )







|

|







 







|
|




>
>
>
>
>
>
>




|







 







|
>
>
>
>
>
>



|












|








|







 







|
|



>







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
...
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
...
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
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
            let
                updatedModel =
                    PackageLoader.update packageLoaderMsg packageLoaderModel
            in
            case updatedModel of
                PackageLoader.Done qualifiedAst ->
                    case typeCheckAndRun entryPoint qualifiedAst of
                        Ok ( wast, isStringReturn ) ->
                            ( ( entryPoint, updatedModel, typeErrors_ )
                            , outgoingPort <| encodeCompilationDone wast isStringReturn
                            )

                        Err ( sourceFilesRequired, errors ) ->
                            ( ( entryPoint, updatedModel, errors )
                            , outgoingPort <| encodeReadFilesToReportError sourceFilesRequired
                            )

................................................................................
            in
            ( model
            , outgoingPort <|
                encodeCompilationFailure errorMessages
            )


legalIntEntryPointType : Type.FunctionType
legalIntEntryPointType =
    { input = []
    , output = [ Type.Int ]
    }


legalStringEntryPointType : Type.FunctionType
legalStringEntryPointType =
    { input = []
    , output = [ Type.Custom "/stabel/standard_library/string/String" ]
    }


typeCheckAndRun :
    Maybe String
    -> Qualifier.AST
    -> Result ( List String, List TypeCheckerProblem.Problem ) ( String, Bool )
typeCheckAndRun entryPoint qualifiedAst =
    case TypeChecker.run qualifiedAst of
        Err typeErrors ->
            let
                sourceFiles =
                    typeErrors
                        |> List.map TypeCheckerProblem.sourceLocationRef
................................................................................

                entryPointFunction =
                    entryPoint
                        |> Maybe.andThen (\n -> Dict.get n typedAst.functions)
            in
            case entryPointFunction of
                Just fn ->
                    if fn.type_ == legalIntEntryPointType then
                        typedAst
                            |> Codegen.run exportedFunctions
                            |> Wasm.toString
                            |> (\wat -> Ok ( wat, False ))

                    else if fn.type_ == legalStringEntryPointType then
                        typedAst
                            |> Codegen.run exportedFunctions
                            |> Wasm.toString
                            |> (\wat -> Ok ( wat, True ))

                    else
                        let
                            sourceLoc =
                                fn.sourceLocation
                                    |> Maybe.withDefault SourceLocation.emptyRange
                        in
                        Err
                            ( [ sourceLoc.source ]
                            , [ TypeCheckerProblem.BadEntryPoint
                                    sourceLoc
                                    fn.name
                                    legalIntEntryPointType
                                    fn.type_
                              ]
                            )

                Nothing ->
                    typedAst
                        |> Codegen.run exportedFunctions
                        |> Wasm.toString
                        |> (\wat -> Ok ( wat, False ))



-- Json Encoding/Decoding


encodeSideEffectAsJson : PackageLoader.SideEffect -> Json.Value
................................................................................
            Encode.object
                [ ( "type", Encode.string "resolvePackageModules" )
                , ( "package", Encode.string moduleName )
                , ( "path", Encode.string path )
                ]


encodeCompilationDone : String -> Bool -> Json.Value
encodeCompilationDone wast isStringReturn =
    Encode.object
        [ ( "type", Encode.string "compilationDone" )
        , ( "wast", Encode.string wast )
        , ( "isStringReturned", Encode.bool isStringReturn )
        ]


encodeCompilationFailure : String -> Json.Value
encodeCompilationFailure errorMsg =
    Encode.object
        [ ( "type", Encode.string "compilationFailure" )

Added stdlib/src/string.stbl version [cbd931c538].

























>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
defmodule:
exposing: 
  String
  from-bytes
:

defstruct: String
: content (Array Int)

def: from-bytes
type: (Array Int) -- String
: >String

Modified stdlib/stabel.json from [d1c473ccb1] to [3deb26f18d].

2
3
4
5
6
7
8
9

10
11
12
13
    "name": "stabel/standard_library",
    "version": "0.2.1",
    "language-version": "0.2.0",
    "exposed-modules": [
        "core",
        "maybe",
        "pair",
        "list"

    ],
    "dependencies": {},
    "package-paths": []
}







|
>




2
3
4
5
6
7
8
9
10
11
12
13
14
    "name": "stabel/standard_library",
    "version": "0.2.1",
    "language-version": "0.2.0",
    "exposed-modules": [
        "core",
        "maybe",
        "pair",
        "list",
        "string"
    ],
    "dependencies": {},
    "package-paths": []
}

Modified wasm_tests/compiler.wrapper.js from [86e63f9b44] to [d06acbe156].

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
        // The first three I32 positions are used for stack and heap information
        // The fourth position is the first element of the stack
        return this.memoryView[3 + (index || 0)];
    }

    stringElement(index) {
        // String structure => typeId | array 
        const strPointer = this.stackElement(index || 0);
        const strOffset = strPointer / 4;

        const arrayPointer = this.memoryView[strOffset + 1];
        const arrayOffset = arrayPointer / 4;

        const strLen = this.memoryView[arrayOffset];








|







115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
        // The first three I32 positions are used for stack and heap information
        // The fourth position is the first element of the stack
        return this.memoryView[3 + (index || 0)];
    }

    stringElement(index) {
        // String structure => typeId | array 
        const strPointer = this.stackElement(index);
        const strOffset = strPointer / 4;

        const arrayPointer = this.memoryView[strOffset + 1];
        const arrayOffset = arrayPointer / 4;

        const strLen = this.memoryView[arrayOffset];