diff --git a/.fvmrc b/.fvmrc index b987073..3ca65ff 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.29.3" + "flutter": "3.32.8" } \ No newline at end of file diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..d734595 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,38 @@ +name: Update Docs +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + update_docs: + runs-on: ubuntu-latest + steps: + - name: πŸ“š Checkout code + uses: actions/checkout@v3 + + - name: 🐦 Set up Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: πŸ“¦ Install dependencies + run: dart pub get + + + - name: πŸ“– Generate documentation + run: dart bin/doc.dart + + - uses: fregante/setup-git-user@v2 + - name: πŸ“€ Commit and push changes + run: | + if [ -z "$(git status --porcelain)" ]; then + echo "No changes to commit." + exit 0 + fi + git commit -am "docs: update documentation [skip ci]" + git push + + + \ No newline at end of file diff --git a/.github/workflows/report.yaml b/.github/workflows/report.yaml index bc78217..efc34d4 100644 --- a/.github/workflows/report.yaml +++ b/.github/workflows/report.yaml @@ -1,4 +1,5 @@ - +name: Report Test Results + on: workflow_run: workflows: ["Unit Tests"] @@ -22,8 +23,6 @@ jobs: name: πŸ§ͺ Unit Tests path: test-results.json reporter: dart-json - fail-on-empty: true - fail-on-error: true max-annotations: 50 \ No newline at end of file diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index cb87912..1adb9bf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -37,7 +37,7 @@ jobs: - name: πŸ“ Check Test Results run: | - if grep -q '"result":"failure"' test-results.json; then + if grep -q '"result":"error"' test-results.json; then echo "❌ Tests failed" exit 1 fi diff --git a/.gitignore b/.gitignore index bc0376d..f5c587a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ *.interp *.tokens coverage/ +custom_lint.log diff --git a/.versionrc b/.versionrc index 10eb807..3b9cca7 100644 --- a/.versionrc +++ b/.versionrc @@ -22,5 +22,14 @@ ], "packageFiles": [ "pubspec.yaml" - ] + ], + "parserOpts": { + "noteKeywords": [ + "BREAKING CHANGE", + "BREAKING CHANGES", + "BREAKING", + "BREAKING-CHANGE", + "BREAKING-CHANGES" + ] + } } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 806e888..59dee9f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,11 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.29.3", + "dart.flutterSdkPath": ".fvm/versions/3.32.8", "conventionalCommits.scopes": [ "analyzer", "compiler", "runtime", - "vm" + "vm", + "stdlib" ], "conventionalCommits.autoCommit": false, "conventionalCommits.gitmoji": false, diff --git a/README.md b/README.md index 30f998e..c327b39 100644 --- a/README.md +++ b/README.md @@ -6,36 +6,15 @@ A lightweight [Domain-Specific Language (DSL)](https://en.wikipedia.org/wiki/Dom --- -## Getting Started - -Define a script: - -```dscript -author "You"; -version 0.1.0; -name "Example"; +## Motivation -description "demo"; +For many applications I've built, I wished it was possible for users to extend the app with custom code, such as plugins or scripts. However, during my research I found out that dart does not support dynamic code execution like C# or Java using DLLs or JARs. A different approach would be to have plugins exist as seperate executables and communicate with the main app via grpc, but this would expose the app to severe security risks, as the plugin could do anything it wants once the executable is running. -contract Demo { - impl greet(string who) -> void { - log::info("Hello " + who); - } -} -``` +So I looked at [lua_dardo](https://pub.dev/packages/lua_dardo) and [dart_eval](https://pub.dev/packages/dart_eval) to see if I could use them to run scripts, but they were not suitable for my use case as lua lacked the strong permission system I wanted, and dart_eval seemed to have too much boilerplate for my taste. After long consideration and hesitation, I decided to create my own DSL that would be easy to use, have a strong permission system, and with strong interop with Dart code (it was also a good opportunity to learn about writing parsers and interpreters). -Register the contract in your host app: - -```dart -final demo = contract('Demo') - .impl('greet', returnType: PrimitiveType.VOID) - .param('who', PrimitiveType.STRING) - .describe("Greets someone") - .end() - .build(); -``` +## Getting Started -For full documentation see the [docs](https://mcquenji.github.io/dscript). +The documentation can be found [here](https://mcquenji.github.io/dscript). ## Contributing diff --git a/analysis_options.yaml b/analysis_options.yaml index 0cf0184..57231a1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -28,6 +28,8 @@ linter: analyzer: exclude: - lib/src/gen/antlr/** + plugins: + - custom_lint # For more information about the core and recommended set of lints, see # https://dart.dev/go/core-lints diff --git a/bin/doc.dart b/bin/doc.dart new file mode 100644 index 0000000..5bbaa1f --- /dev/null +++ b/bin/doc.dart @@ -0,0 +1,162 @@ +import 'dart:io'; +import 'package:dscript_dart/dscript_dart.dart'; + +void main() async { + final sb = StringBuffer(); + + sb.writeln('# Standard Library'); + sb.writeln(); + // Generate Table of Contents + sb.writeln(); + final libraries = LibraryBinding.stdLib(); + for (final lib in libraries) { + final anchor = _toAnchor(lib.name); + sb.writeln('- [${lib.name}](#$anchor)'); + } + sb.writeln('---'); + + sb.writeln(); + sb.writeln('## Globals'); + sb.writeln(); + sb.writeln( + 'The following globals are available in the dscript runtime. They are not part of any library.', + ); + sb.writeln(); + sb.writeln(); + + sb.writeln('| Name | Type | Value |'); + sb.writeln('| --- | --- | --- |'); + + for (final glob in TypeScope.globals.entries) { + sb.writeln('| ${glob.key} | `${glob.value.$2}` | ${glob.value.$1} |'); + } + + sb.writeln(); + + sb.writeln('## Structs'); + + for (final struct in Struct.defaults) { + sb.writeln(); + sb.writeln('### ${struct.name}'); + sb.writeln(); + sb.writeln(struct.description?.docstring); + sb.writeln(); + + if (struct.fields.isNotEmpty) { + sb.writeln('| Field | Type |'); + sb.writeln('| --- | --- |'); + for (final field in struct.fields.entries) { + sb.writeln('| ${field.key} | `${field.value}` |'); + } + } else { + sb.writeln('This struct has no fields.'); + } + } + + sb.writeln(); + sb.writeln(); + + // Document each library + for (final lib in libraries) { + sb.writeln(); + sb.writeln('---'); + sb.writeln(); + sb.writeln(_documentLibrary(lib)); + } + + final output = sb.toString(); + final file = File('docs/language/standard-library.md'); + await file.writeAsString(output); +} + +String _toAnchor(String text) { + return text + .trim() + .toLowerCase() + .replaceAll(RegExp(r'[^a-z0-9\s-]'), '') + .replaceAll(RegExp(r'\s+'), '-'); +} + +String _documentLibrary(LibraryBinding lib) { + final sb = StringBuffer(); + + sb.writeln('## ${lib.name}'); + sb.writeln(); + sb.writeln(lib.description.docstring); + sb.writeln(); + + for (final binding in lib.bindings) { + sb.writeln(_documentBinding(binding)); + sb.writeln(); + } + + return sb.toString(); +} + +String _documentBinding(RuntimeBinding binding) { + final sb = StringBuffer(); + + // Binding Header with anchor + sb.writeln('### ${binding.name} → `${binding.returnType}`'); + + // Combine positional and named params in one table + final params = >[]; + for (var i = 0; i < binding.positionalParams.length; i++) { + final p = binding.positionalParams.entries.elementAt(i); + params.add( + {'name': p.key, 'type': p.value, 'kind': 'Positional (${i + 1})'}, + ); + } + for (final entry in binding.namedParams.entries) { + final name = entry.key + .toString() + .substring('Symbol("'.length, entry.key.toString().length - 2); + params.add({ + 'name': name, + 'kind': 'Named', + 'type': entry.value.toString(), + }); + } + + if (params.isNotEmpty) { + sb.writeln(); + sb.writeln('| Name | Type | Kind |'); + sb.writeln('| --- | --- | --- |'); + for (final p in params) { + sb.writeln('| ${p['name']} | `${p['type']}` | ${p['kind']} |'); + } + } + + if (binding.description != null && binding.description!.isNotEmpty) { + final description = binding.description!.docstring; + + if (description.contains('\n')) { + final summary = description.split('\n').first; + final details = description.substring(summary.length).trim(); + sb.writeln('
'); + sb.writeln('$summary'); + sb.writeln('$details'); + sb.writeln('
'); + sb.writeln('
'); + } else { + sb.writeln(); + sb.writeln('$description'); + } + } + + if (binding.permissions.isNotEmpty) { + sb.writeln(); + sb.writeln('#### Permissions'); + sb.writeln(); + sb.writeln(binding.permissions.map((p) => '`$p`').join(', ')); + } + + return sb.toString(); +} + +extension on String { + String get docstring => trim().replaceAllMapped( + RegExp(r'\[([^\]]+)\]'), + (m) => '${m.group(1)}', + ); +} diff --git a/bin/dscript.dart b/bin/dscript.dart index 6f198ce..6f5fc14 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -5,6 +5,17 @@ import 'package:antlr4/antlr4.dart'; import 'package:dscript_dart/dscript_dart.dart'; import 'package:logging/logging.dart'; +class User { + final int id; + + final String name; + + const User({ + required this.id, + required this.name, + }); +} + void main(List arguments) async { Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { @@ -20,6 +31,14 @@ void main(List arguments) async { ); }); + HttpBindings.getBinding.addPreMiddleware(({ + required binding, + required positionalArgs, + required namedArgs, + }) { + print('Sending GET request to ${positionalArgs[0]}'); + }); + final code = await InputStream.fromPath('./bin/test.dscript'); final script = analyze( @@ -39,7 +58,7 @@ void main(List arguments) async { .hook('onLogin') .param( 'user', - const Struct(name: 'User'), + const Struct.shallow('User'), ) .describe( 'Event emitted when a user logs in.', @@ -51,15 +70,24 @@ void main(List arguments) async { ) .end() .bind('double', (int x) => x * 2) - .param(PrimitiveType.INT) + .returns(PrimitiveType.DOUBLE) + .param('x', PrimitiveType.INT) .describe( 'A simple function that doubles an integer.', ) .permission('math') .end() - .struct('User') + .struct('User') .field('name', PrimitiveType.STRING) .field('id', PrimitiveType.INT) + .fromDart((u) => { + 'id': u.id, + 'name': u.name, + }) + .toDart((data) => User( + id: data['id'] as int, + name: data['name'] as String, + )) .end() .build(), ], @@ -88,4 +116,10 @@ void main(List arguments) async { 'randomNumber', args: {'foo': 42}, )); + + print(bytecode.implementations['randomString']!.toDebugString()); + + print(await runtime.run( + 'randomString', + )); } diff --git a/bin/test.dscript b/bin/test.dscript index 246d8d4..683daed 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -1,4 +1,4 @@ -schema ""; +schema ""; author "McQuenji"; version 0.0.1; @@ -15,6 +15,7 @@ permissions external::math; contract Random { const bar = 5; + const srcFile = "bin/test.dscript"; func test(string name) { @@ -22,12 +23,12 @@ contract Random { } + impl randomNumber(int foo) -> double { - const l = pi * 5; if(5.0 == l) { return l; - } + } else if(5 > foo && foo < 10) { return 4 * bar; }else { @@ -37,43 +38,44 @@ contract Random { } - impl randomString() -> string - { - while(true) { - log::info("Generating random string..."); - break; + impl randomString() -> string { + for (final int i in [1, 2, 3, 4, 5]) { + log::info("Iteration: " + i); } - - - // - // try { - // log::info("test"); - // return 'test'; - // } catch (e) { - // log::error("Error", error: e); - // return e.message; - // } - - return "RandomString"; + ///try { + /// log::info("Reading file: " + fs::absolute(srcFile)); + /// final contents = fs::read(srcFile); + /// log::info("Successfully read file"); + /// + /// return contents; + ///} catch(e) { + /// log::error("Error reading file", error: e); + /// return "Error"; + ///} + + return "RandomString"; } + + impl test() -> void { final string? test = "Test"; + return; + var string? nonNullable; final bool test2 = false; - //test = "Test2"; - var p = 1.3; + var p = 1.0; p = 2.0; return; - } + } hook onLogout() { // Hook implementation diff --git a/custom_lints/.gitignore b/custom_lints/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/custom_lints/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/custom_lints/README.md b/custom_lints/README.md new file mode 100644 index 0000000..c947239 --- /dev/null +++ b/custom_lints/README.md @@ -0,0 +1,7 @@ +# custom_lints + +Internal lint rules for Dscript core development to enforce project-specific coding constraints. + +Currently includes: + +- **`no_type_to_string`** β€” bans calling `.toString()` on a `Type` (including `runtimeType.toString()`), which breaks in web builds due to Dart’s minified type names. diff --git a/custom_lints/analysis_options.yaml b/custom_lints/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/custom_lints/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/custom_lints/lib/custom_lints.dart b/custom_lints/lib/custom_lints.dart new file mode 100644 index 0000000..d931a2a --- /dev/null +++ b/custom_lints/lib/custom_lints.dart @@ -0,0 +1,53 @@ +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/error/error.dart' hide LintCode; + +PluginBase createPlugin() => _MyPlugin(); + +class _MyPlugin extends PluginBase { + @override + List getLintRules(CustomLintConfigs configs) => [_NoTypeToString()]; +} + +class _NoTypeToString extends DartLintRule { + _NoTypeToString() + : super( + code: LintCode( + name: 'no_type_to_string', + problemMessage: + 'Avoid calling `toString()` on a `Type`. In web builds, type names are minified and cannot be reliably mapped to Dscript types.', + errorSeverity: ErrorSeverity.ERROR, + correctionMessage: + r"Use a stable Dscript type reference instead (e.g. `$Type.Primitive.INT`) or hardcode the string (e.g. `$Type.from('int')`).", + ), + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addMethodInvocation((MethodInvocation node) { + final target = node.realTarget ?? node.target; + if (target == null) return; + + final isToString = node.methodName.name == 'toString'; + final noArgs = node.argumentList.arguments.isEmpty; + if (!isToString || !noArgs) return; + + final t = target.staticType; + + final isTypeObject = + t is InterfaceType && + t.element3.library2.name3 == 'dart.core' && + t.element3.name3 == 'Type'; + + if (isTypeObject) { + reporter.atNode(node, code); + } + }); + } +} diff --git a/custom_lints/pubspec.yaml b/custom_lints/pubspec.yaml new file mode 100644 index 0000000..35e52fd --- /dev/null +++ b/custom_lints/pubspec.yaml @@ -0,0 +1,14 @@ +name: custom_lints +description: Internal lint rules for Dscript core development to enforce project-specific coding constraints. +version: 1.0.0 + +environment: + sdk: ^3.7.2 + +dependencies: + custom_lint_builder: ^0.8.0 + analyzer: ^7.6.0 + +dev_dependencies: + lints: ^5.0.0 + test: ^1.24.0 diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 2f8153b..bb83e50 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -13,3 +13,5 @@ - Advanced - [Compiler](advanced/compiler.md) - [Choosing a Runtime](advanced/runtimes.md) + - [Middlewares](advanced/middlewares.md) + - [Extending the Standard Library](advanced/stdlib.md) diff --git a/docs/advanced/compiler.md b/docs/advanced/compiler.md index dae40fa..9c2da82 100644 --- a/docs/advanced/compiler.md +++ b/docs/advanced/compiler.md @@ -1,15 +1,14 @@ # Compiler Overview -Dscript includes an optional bytecode compiler that transforms the -analyzed abstract syntax tree (AST) into a compact representation. -The compiler currently targets a simple stack based virtual machine. +Dscript includes a bytecode compiler that transforms the +analyzed abstract syntax tree (AST) into a compact representation, which is then executed by the runtime via a virtual machine. ```mermaid flowchart LR subgraph Analysis A[Parsed AST] - end A --> B{Analyzer} + end B -->|verified script| C[Compiler] C --> D[Bytecode] D -.-> E[Runtime] @@ -45,4 +44,4 @@ supplying a custom `Compiler` implementation to the `compile` function. ```dart final compiled = compile(script, (globals) => MyCompiler(globals)); -``` \ No newline at end of file +``` diff --git a/docs/advanced/middlewares.md b/docs/advanced/middlewares.md new file mode 100644 index 0000000..877316a --- /dev/null +++ b/docs/advanced/middlewares.md @@ -0,0 +1,52 @@ +# Middlewares + +You can plug middlewares into any `RuntimeBinding` to extend its behavior (e.g. for logging, validation or transforming data). Middlewares run in the order you add them, passing along any changes until finally invoking the bound function and returning to the caller. + +```mermaid +flowchart-elk LR + A[Pre Middlewares] -->|Modified args| B[Bound Function] + B -->|Result| C[Post Middlewares] + C -->|Modified result| D[Caller] +``` + +## Pre Middlewares + +Runs *before* the bound function. Use these to tweak or validate inputs. If one throws, the binding stops and the error bubbles up. + +```dart +// Example: log reads and block secret files +FsBindings.readFileBinding.addPreMiddleware(({ + required binding, + required positionalArgs, + required namedArgs, +}) { + final path = positionalArgs[0] as String; + print('Reading file $path'); + + if (path == 'secret-file.txt') { + throw Exception('This file is not allowed to be read!'); + } + + positionalArgs[0] = path.replaceAll('secret', 'public'); +}); +``` + +## Post Middlewares + +Runs *after* the bound function. Use these to inspect or transform outputs before sending them back. + +```dart +// Example: redact secret files in the response +FsBindings.readFileBinding.addPostMiddleware(({ + required binding, + required positionalArgs, + required namedArgs, + required result, +}) { + final path = positionalArgs[0] as String; + if (path == 'secret-file.txt') { + return '[Redacted]'; + } + return result; +}); +``` diff --git a/docs/advanced/runtimes.md b/docs/advanced/runtimes.md index 05c77ac..7695137 100644 --- a/docs/advanced/runtimes.md +++ b/docs/advanced/runtimes.md @@ -2,6 +2,8 @@ Dscript ships with two runtime implementations: `Runtime` and `IsolateRuntime`. Both execute compiled scripts but differ in how they isolate script code. +!> As a result of each hook or implementation being executed independently in seperate VMs (regardless of the runtime) and to avoid race conditions, it is not possible to share state between them. If you need to share data, consider passing it explicitly as an argument. + ## Runtime The default `Runtime` runs code in the current isolate. Host functions are invoked directly which yields the best performance but means that the script shares memory with the host application. @@ -10,6 +12,8 @@ The default `Runtime` runs code in the current isolate. Host functions are invok `IsolateRuntime` uses Dart's `Isolate.run` to execute each invocation in a separate isolate. This provides memory isolation between script and host but comes with additional overhead due to message passing. Use `IsolateRuntime` when scripts should not affect the host isolate or when they perform heavy work that may block. +!> Note that the `IsolateRuntime` does not support any logging or debugging features, as these rely on the host isolate's context. If you need logging, use `Runtime` instead. + ## Custom Virtual Machines The runtime uses a bytecode virtual machine (VM) to execute compiled functions. A different VM can be provided by passing a `vmFactory` when constructing a runtime: diff --git a/docs/advanced/stdlib.md b/docs/advanced/stdlib.md new file mode 100644 index 0000000..4b9650c --- /dev/null +++ b/docs/advanced/stdlib.md @@ -0,0 +1,30 @@ +# Extending the Standard Library + +The standard library is defined as a static function in the `LibraryBinding` class: + +```dart + static StdLib standardLibrary = defaultStdLib; + + typedef StdLib = List Function(ScriptMetadata metadata); +``` + +You can override the default standard library by setting the `standardLibrary` property to a custom function that returns a List of `LibraryBinding`s. This must be done **before** analyzing, compiling, or running any scripts. + +```dart +LibraryBinding.standardLibrary = (metadata) { + return [ + // your custom library bindings here + ]; +}; +``` + +!> This will replace the entire standard library, so you must include all the libraries you want to use in your scripts. If you want to extend the standard library instead of replacing it, add `LibraryBinding.defaultStdLib` to your custom list: + +```dart +LibraryBinding.standardLibrary = (metadata) { + return [ + ...LibraryBinding.defaultStdLib(metadata), + // your custom library bindings here + ]; +}; +``` diff --git a/docs/host/external-functions.md b/docs/host/external-functions.md index 9c29cf7..f7b288f 100644 --- a/docs/host/external-functions.md +++ b/docs/host/external-functions.md @@ -19,7 +19,7 @@ When registering an external function the host maps a permission to one or more ```dart final contract = contract('foo') .bind('uploadFile', (String path) async {// do something} ) - .param(PrimitiveType.STRING) + .param('path', PrimitiveType.STRING) .describe( "Uploads a file to the user's cloud storage", ) diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index ac9a339..3e7dc81 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -1,16 +1,1370 @@ # Standard Library -The analyzer knows about a few built‑in namespaces. Only a subset of functions are currently operational: -## `math::` -Basic numeric helpers like `math::sqrt`, `math::floor` and `math::abs`. +- [math](#math) +- [string](#string) +- [fs](#fs) +- [list](#list) +- [map](#map) +- [dynamic](#dynamic) +- [http](#http) +- [json](#json) +- [utf8](#utf8) +- [base64](#base64) +- [log](#log) +--- -## `string::` -Functions for string conversion and manipulation such as `string::from` and `string::length`. +## Globals + +The following globals are available in the dscript runtime. They are not part of any library. + + +| Name | Type | Value | +| --- | --- | --- | +| pi | `double` | 3.141592653589793 | +| e | `double` | 2.718281828459045 | +| sqrt2 | `double` | 1.4142135623730951 | +| sqrt1_2 | `double` | 0.7071067811865476 | +| log2e | `double` | 1.4426950408889634 | +| log10e | `double` | 0.4342944819032518 | +| ln2 | `double` | 0.6931471805599453 | +| ln10 | `double` | 2.302585092994046 | + +## Structs + +### Error + +Represents an error with a message and stack trace. + +| Field | Type | +| --- | --- | +| message | `string` | +| stackTrace | `string?` | + +### HttpResponse + +Represents an HTTP response with status code, headers, and body. + +| Field | Type | +| --- | --- | +| statusCode | `int` | +| headers | `Map>` | +| data | `string?` | +| statusMessage | `string?` | +| isRedirect | `bool` | + +### JSON + +Result of json::decode. It's either a Map or a List. + +| Field | Type | +| --- | --- | +| map | `Map?` | +| list | `List?` | +| isMap | `bool` | +| isList | `bool` | + +### Duration + +Represents a duration of time. + +| Field | Type | +| --- | --- | +| days | `int` | +| hours | `int` | +| minutes | `int` | +| seconds | `int` | +| milliseconds | `int` | +| microseconds | `int` | + +### DateTime + +Represents a date and time. + +| Field | Type | +| --- | --- | +| year | `int` | +| month | `int` | +| day | `int` | +| hour | `int` | +| minute | `int` | +| second | `int` | +| millisecond | `int` | +| microsecond | `int` | + + + +--- + +## math + +Library for mathematical functions. + +### sqrt → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the positive square root of the value + + +### pow → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | +| y | `double` | Positional (2) | +
+Returns x to the power of exponent. +If x is an int and exponent is a non-negative int, the result is an int, otherwise both arguments are converted to doubles first, and the result is a double. + +For integers, the power is always equal to the mathematical result of x to the power exponent, only limited by the available memory. + +For doubles, pow(x, y) handles edge cases as follows: + +if y is zero (0.0 or -0.0), the result is always 1.0. +if x is 1.0, the result is always 1.0. +otherwise, if either x or y is NaN, then the result is NaN. +if x is negative (but not -0.0) and y is a finite non-integer, the result is NaN. +if x is Infinity and y is negative, the result is 0.0. +if x is Infinity and y is positive, the result is Infinity. +if x is 0.0 and y is negative, the result is Infinity. +if x is 0.0 and y is positive, the result is 0.0. +if x is -Infinity or -0.0 and y is an odd integer, then the result is -pow(-x ,y). +if x is -Infinity or -0.0 and y is not an odd integer, then the result is the same as pow(-x , y). +if y is Infinity and the absolute value of x is less than 1, the result is 0.0. +if y is Infinity and x is -1, the result is 1.0. +if y is Infinity and the absolute value of x is greater than 1, the result is Infinity. +if y is -Infinity, the result is 1/pow(x, Infinity). +This corresponds to the pow function defined in the IEEE Standard 754-2008. + +Notice that the result may overflow. If integers are represented as 64-bit numbers, an integer result may be truncated, and a double result may overflow to positive or negative infinity. +
+
+ + +### log → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the natural logarithm of the value. + + +### exp → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the natural exponent, e, to the power x. + + +### sin → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the sine of the value. + + +### cos → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the cosine of the value. + + +### tan → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | +
+Converts x to a double and returns the tangent of the value. +The tangent function is equivalent to sin(x)/cos(x) +
+
+ + +### asin → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc sine in radians. + + +### acos → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc cosine in radians. + + +### atan → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc tangent in radians. + + +### atan2 → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | +| y | `double` | Positional (2) | +
+A variant of atan. +Converts both arguments to doubles. + +Returns the angle in radians between the positive x-axis and the vector (b,a). The result is in the range -PI..PI. + +If b is positive, this is the same as atan(a/b). + +The result is negative when a is negative (including when a is the double -0.0). + +If a is equal to zero, the vector (b,a) is considered parallel to the x-axis, even if b is also equal to zero. The sign of b determines the direction of the vector along the x-axis. +
+
+ + +### abs → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the absolute value of x. + + +### floor → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the largest integer less than or equal to x. + + +### ceil → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the smallest integer greater than or equal to x. + + +### round → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Rounds x number to the nearest integer. + + +### clamp → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | +| min | `double` | Named | +| max | `double` | Named | + +Clamps x number between a min and max value. + + +### min → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| a | `double` | Positional (1) | +| b | `double` | Positional (2) | + +Returns the minimum of a and b. + + +### max → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| a | `double` | Positional (1) | +| b | `double` | Positional (2) | + +Returns the maximum of a and b. + + +### rad → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| degrees | `double` | Positional (1) | + +Converts degrees to radians. + + +### deg → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| radians | `double` | Positional (1) | + +Converts radians to degrees. + + + + +--- + +## string + +Library for working with strings. + +### length → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Returns the length of str. + + +### substring → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| start | `int` | Positional (2) | +| end | `int?` | Named | +
+The substring of str from start, inclusive, to end, exclusive. +Both start and end must be non-negative and no greater than the string's length; end, if provided, must be greater than or equal to start. + If end is omitted, the substring extends to the end of the string. +
+
+ + +### upper → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Converts str to uppercase. + + +### lower → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Converts str to lowercase. + + +### trim → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Removes leading and trailing whitespace from str and returns the resulting string. + + +### split → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Splits str into a list of substrings using pattern as the delimiter. + + +### replaceAll → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| from | `string` | Positional (2) | +| to | `string` | Positional (3) | + +Replaces all occurrences of from with to in str. + + +### contains → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str contains pattern. False otherwise. + + +### startsWith → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str starts with pattern. False otherwise. + + +### endsWith → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str ends with pattern. False otherwise. + + +### indexOf → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns the index of the first occurrence of pattern in str. + + +### lastIndexOf → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns the index of the last occurrence of pattern in str. + + +### replaceFirst → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| from | `string` | Positional (2) | +| to | `string` | Positional (3) | + +Replaces the first occurrence of from with to in str. + + +### from → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| obj | `dynamic?` | Positional (1) | + +String representation of obj. If obj is a string, it is returned unchanged; otherwise, it is stringfied. + + +### fromCharCode → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| code | `int` | Positional (1) | + +Creates a string from a single character code code. + + +### from → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| codes | `List` | Positional (1) | + +Creates a string from a list of character codes codes. + + + + +--- + +## fs + +Library for interacting with the file system. + +### read → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Reads the contents of a file at the given path as a string. + +#### Permissions + +`fs::read` + + +### write → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| append | `bool?` | Named | +| path | `string` | Named | +| content | `string` | Named | + +Writes a string to a file at the given path. If append is true, it appends to the file; otherwise, it overwrites the file. The file is created if it does not exist. + +#### Permissions + +`fs::write` + + +### delete → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Deletes a file at the given path. + +#### Permissions + +`fs::write` + + +### exists → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if a file exists at the given path. + +#### Permissions + +`fs::read` + + +### ls → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Lists the contents of a directory at the given path. Returns a list of file paths. + +#### Permissions + +`fs::read` + + +### mkdir → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Creates a directory at the given path. If the directory already exists, it does nothing. + +#### Permissions + +`fs::write` + + +### isDir → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if the path is a directory. + +#### Permissions + +`fs::read` + + +### isFile → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if the path is a file. + +#### Permissions + +`fs::read` + + +### absolute → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the absolute path of a file or directory at the given path. + +#### Permissions + +`fs::read` + + +### cwd → `string` + +Returns the current working directory. + +#### Permissions + +`fs::read` + + +### move → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| from | `string` | Named | +| to | `string` | Named | + +Moves a file or directory from from to to. + +#### Permissions + +`fs::write`, `fs::read` + + +### copy → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| from | `string` | Named | +| to | `string` | Named | + +Copies a file or directory from from to to. + +#### Permissions + +`fs::write`, `fs::read` + + +### size → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the size of a file at the given path in bytes. + +#### Permissions + +`fs::read` + + +### extension → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the file extension of the given path. Returns an empty string if no extension is found. + + +### basename → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the base name of the given path. + + +### dirname → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +
+The parent path of a path. +Finds the final path component of a path, using the platform's path separator to split the path, and returns the prefix up to that part. + +Will not remove the root component of a Windows path, like "C:\" or "\\server_name\". Includes a trailing path separator in the last part of path, and leaves no trailing path separator. +
+
+ + + + +--- + +## list + +Library for working with lists. + +### length → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns the number of elements in the list. + + +### isEmpty → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns true if the list is empty. + + +### isNotEmpty → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns true if the list is not empty. + + +### add → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Adds element to the end of the list. + + +### addAll → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| elements | `List` | Positional (2) | + +Adds all elements to the end of the list. + + +### clear → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes all elements from the list. + + +### remove → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes the first occurrence of element from the list. + + +### removeAt → `dynamic?` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | + +Removes and returns the element at index from the list. + + +### removeLast → `dynamic?` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes and returns the last element from the list. + + +### insert → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | +| element | `dynamic?` | Positional (3) | + +Inserts element at index in the list. + + +### insertAll → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | +| elements | `List` | Positional (3) | + +Inserts all elements at index in the list. + + +### indexOf → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| element | `dynamic?` | Positional (2) | +| start | `int?` | Named | + +Returns the index of the first occurrence of element in the list, starting from start. + + +### lastIndexOf → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| element | `dynamic?` | Positional (2) | +| start | `int?` | Named | + +Returns the index of the last occurrence of element in the list, starting from start. + + +### contains → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| element | `dynamic?` | Positional (2) | + +Returns true if the list contains element. + + +### copy → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns a copy of the list. + + + + +--- + +## map + +Library for working with maps. + +### length → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns the number of key-value pairs in the map. + + +### isEmpty → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns true if the map is empty. + + +### isNotEmpty → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns true if the map is not empty. + + +### containsKey → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| key | `dynamic?` | Positional (2) | + +Returns true if the map contains the specified key. + + +### containsValue → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| value | `dynamic?` | Positional (2) | + +Returns true if the map contains the specified value. + + +### keys → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a list of all keys in the map. + + +### values → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a list of all values in the map. + + +### addAll → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| other | `Map` | Positional (2) | + +Adds all key-value pairs from other to the map. + + +### clear → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Removes all key-value pairs from the map. + + +### remove → `dynamic?` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| key | `dynamic?` | Positional (2) | + +Removes the key-value pair for the specified key from the map. + + +### keyOf → `dynamic?` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| value | `dynamic?` | Positional (2) | + +Returns the first key associated with the specified value. + + +### keysOf → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| value | `dynamic?` | Positional (2) | + +Returns a list of keys associated with the specified value. + + +### entries → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a list of key-value pairs in the map. + + +### copy → `Map` + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a copy of the map. + + + + +--- + +## dynamic + +Various utilities for dynamic values. + +### toString → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a string. + + +### toInt → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to an int. + + +### toDouble → `double` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a double. + + +### toBool → `bool` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a bool. + + +### length → `int` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Returns the length of value if it is a collection or string, otherwise throws. + + +### type → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Returns the type of value as a string. + + + + +--- + +## http + +Library for making HTTP requests. + +### get → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a GET request to the specified path with optional query parameters and headers. + +#### Permissions + +`http::client` + + +### post → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| data | `dynamic?` | Named | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a POST request to the specified path with optional data, query parameters, and headers. + +#### Permissions + +`http::client` + + +### put → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| data | `dynamic?` | Named | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a PUT request to the specified path with optional data, query parameters, and headers. + +#### Permissions + +`http::client` + + +### delete → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| data | `dynamic?` | Named | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a DELETE request to the specified path with optional data, query parameters, and headers. + +#### Permissions + +`http::client` + + +### patch → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| data | `dynamic?` | Named | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a PATCH request to the specified path with optional data, query parameters, and headers. + +#### Permissions + +`http::client` + + +### head → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes a HEAD request to the specified path with optional query parameters and headers. + +#### Permissions + +`http::client` + + +### options → `HttpResponse` + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | +| queryParams | `Map?` | Named | +| headers | `Map?` | Named | + +Makes an OPTIONS request to the specified path with optional query parameters and headers. + +#### Permissions + +`http::client` + + + + +--- + +## json + +Provides JSON encoding and decoding functions. + +### decode → `JSON` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +
+Deserializes the given str to a JSON object. +Throws an error if the string is not valid JSON or cannot be parsed. +
+
+ + +### encode → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| obj | `dynamic?` | Positional (1) | +
+Serializes the given obj to a JSON string. +Throws an error if the object cannot be serialized. +
+
+ + + + +--- + +## utf8 + +Provides UTF-8 encoding and decoding functions. + +### decode → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| bytes | `List` | Positional (1) | + +Decodes the given bytes to a UTF-8 string. + + +### encode → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Encodes the given str to a list of UTF-8 bytes. + + + + +--- + +## base64 + +Provides utility functions for Base64 encoding and decoding. + +### encode → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| input | `string` | Positional (1) | + +Encodes input to Base64 format. + + +### decode → `string` + +| Name | Type | Kind | +| --- | --- | --- | +| input | `string` | Positional (1) | + +Decodes input from Base64 format. + + + + +--- + +## log + +Provides basic logging utitlities. + +!> Log messages will not be visible when using IsolateRuntime. + +### info → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | + +Logs general informational messages that highlight the progress of the application at a coarse level. + + +### warning → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | + +Logs potential problems that are not yet errors but might require attention or could lead to issues. + + +### severe → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs serious failures or errors that will likely +prevent normal program execution. +
+
+ + +### fine → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs tracing information for debugging purposes. +Less verbose than finer or finest. +
+
+ + +### finer → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs fairly detailed tracing information. +Useful when debugging complex flows with more granularity than fine. +
+
+ + +### finest → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs highly detailed tracing information. +Intended for deep debugging, usually too verbose for normal use. +
+
+ + +### config → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs static configuration messages. +Typically used to record startup settings or environment details. +
+
+ + +### shout → `void` + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs messages at the shout level. +Louder than severe; use sparingly for attention-grabbing events. +
+
-## `log::` -Logging helpers: `log::info`, `log::warning` and `log::error` simply print to the host console. -## Not Yet Implemented -The `fs::` and `http::` namespaces are defined in the grammar but are not yet implemented. Calls to these functions will raise analysis errors or runtime errors until support is added. diff --git a/lib/dscript_dart.dart b/lib/dscript_dart.dart index 97f2714..ac79a13 100644 --- a/lib/dscript_dart.dart +++ b/lib/dscript_dart.dart @@ -6,3 +6,4 @@ export 'src/contract_builder.dart'; export 'src/compiler/compiler.dart'; export 'src/vm/vm.dart'; export 'src/runtime/runtime.dart'; +export 'src/stdlib/stdlib.dart'; diff --git a/lib/src/analyzer/visitors/expressions.dart b/lib/src/analyzer/visitors/expressions.dart index 97c53f4..fdbfce7 100644 --- a/lib/src/analyzer/visitors/expressions.dart +++ b/lib/src/analyzer/visitors/expressions.dart @@ -403,7 +403,7 @@ class ExprVisitor extends AnalysisVisitor { } for (var i = 0; i < posArgs.length; i++) { - final expected = impl.positionalParams[i]; + final expected = impl.positionalParams.values.elementAt(i); final found = posArgs[i]; if (!found.canCast(expected)) { diff --git a/lib/src/analyzer/visitors/flow.dart b/lib/src/analyzer/visitors/flow.dart index 2e457e6..8afb6eb 100644 --- a/lib/src/analyzer/visitors/flow.dart +++ b/lib/src/analyzer/visitors/flow.dart @@ -153,9 +153,9 @@ class FlowVisitor extends AnalysisVisitor { return report(InferenceError(ctx: ctx)); } - if (iterable is! ListType) { + if (iterable is! ListType && iterable != PrimitiveType.STRING) { return report( - SemanticError('Cannot iterate over non-list type', ctx: ctx), + SemanticError('Can only iterate over strings and lists', ctx: ctx), ); } @@ -166,7 +166,7 @@ class FlowVisitor extends AnalysisVisitor { final ident = ctx.varDecl()!.identifier()!.text; scope.set( ident, - iterable.elementType, + iterable is ListType ? iterable.elementType : PrimitiveType.STRING, mutable, ); @@ -176,19 +176,17 @@ class FlowVisitor extends AnalysisVisitor { return const InvalidType(); } - if (ctx.varDecl() != null) { - // For loop with variable declaration - final varType = ctx.varDecl()!.varType()!; + // For loop with variable declaration + final varType = ctx.varDecl()!.varType()!; - final mutable = varType.VAR() != null; + final mutable = varType.VAR() != null; - final ident = ctx.varDecl()!.identifier()!.text; - scope.set( - ident, - PrimitiveType.INT, - mutable, - ); - } + final ident = ctx.varDecl()!.identifier()!.text; + scope.set( + ident, + PrimitiveType.INT, + mutable, + ); ensureConditionIsBool(ctx.expr()); ctx.assignment()?.accept(ExprVisitor(this)); diff --git a/lib/src/analyzer/visitors/visitors.dart b/lib/src/analyzer/visitors/visitors.dart index 596ed9e..71473c9 100644 --- a/lib/src/analyzer/visitors/visitors.dart +++ b/lib/src/analyzer/visitors/visitors.dart @@ -5,7 +5,6 @@ import 'package:dscript_dart/dscript_dart.dart' hide contract; import 'package:dscript_dart/src/gen/antlr/dscriptBaseVisitor.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptLexer.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptParser.dart'; -import 'package:dscript_dart/src/stdlib/stdlib.dart'; import 'package:pub_semver/pub_semver.dart'; part 'metadata.dart'; diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index 45e8d5d..d451992 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -23,7 +23,7 @@ class RuntimeBinding { final List permissions; /// A list of positional parameters in the order they are expected. - final List<$Type> positionalParams; + final Map positionalParams; /// Optional description of the binding. /// @@ -31,26 +31,114 @@ class RuntimeBinding { final String? description; /// The return type of the function as a dsl type. - $Type get returnType => $Type.from(T.toString()); + final $Type returnType; + + /// A list of pre-binding middlewares that are called before the binding's function is executed. + /// + /// These middlewares will be called in the order they are added. + final List> _preMiddlewares = [validateArgs]; + + /// A list of pre-binding middlewares that are called before the binding's function is executed. + /// + /// These middlewares will be called in the order they are added. + final List> _postMiddlewares = []; /// Creates a new [RuntimeBinding] instance. - const RuntimeBinding({ + /// + /// The [returnType] can be specified to override the default return type inferred from [T]. + /// If not specified, it will default to the type of [T]. + /// + /// When returning structs, it is recommended to override the [returnType] to the struct type + /// as the type inference will just return [MapType]. + RuntimeBinding({ required this.name, required this.function, this.namedParams = const {}, this.permissions = const [], - this.positionalParams = const [], + this.positionalParams = const {}, required this.description, - }); + required this.returnType, + List>? preMiddlewares, + List>? postMiddlewares, + }) { + _preMiddlewares.addAll(preMiddlewares ?? []); + _postMiddlewares.addAll(postMiddlewares ?? []); + } + + /// Adds a [PreBindingMiddleware] to this binding called in the order it was added. + /// + /// Any changes made to the arguments in the middleware will be passed to the next middleware in the chain + /// and finally to the bound function. + void addPreMiddleware(PreBindingMiddleware middleware) { + _preMiddlewares.add(middleware); + } + + /// Adds a [PostBindingMiddleware] to this binding called in the order it was added. + /// + /// The result of the added middleware will be passed to the next middleware in the chain + /// and finally to the caller. + void addPostMiddleware(PostBindingMiddleware middleware) { + _postMiddlewares.add(middleware); + } /// Calls the bound function with the provided arguments. - Future call(List positionalArgs, - {Map namedArgs = const {}}) async { + Future call( + List positionalArgs, { + Map namedArgs = const {}, + }) async { positionalArgs = List.from(positionalArgs); namedArgs = Map.from(namedArgs); + // call the pre-middlewares + for (final middleware in _preMiddlewares) { + await middleware( + binding: this, + positionalArgs: positionalArgs, + namedArgs: namedArgs, + ); + } + + var result = await Function.apply( + function, + positionalArgs, + namedArgs, + ); + + final resultType = $Type.fromValue(result); + + if (resultType.canCast(returnType)) { + // Cast the result to the expected return type + result = resultType.cast(returnType, result) as T; + } else { + throw StateError( + 'Invalid return type: expected $returnType, got $resultType', + ); + } + + // call the post-middlewares + + return await _postMiddlewares.fold>( + Future.value(result), + (previous, middleware) async { + final res = await previous; + return middleware( + binding: this, + result: res, + positionalArgs: positionalArgs, + namedArgs: namedArgs, + ); + }, + ); + } + + /// Validates the provided arguments against the binding's parameters. + static void validateArgs({ + required RuntimeBinding binding, + required List positionalArgs, + required Map namedArgs, + }) { // Check if all named parameters are provided - for (final entry in namedParams.entries) { + for (final entry in binding.namedParams.entries) { final param = entry.key; final expectedType = entry.value; @@ -78,45 +166,31 @@ class RuntimeBinding { } // Check if the number of positional arguments matches - if (positionalParams.length != positionalArgs.length) { + if (binding.positionalParams.length != positionalArgs.length) { throw ArgumentError( - 'Invalid number of positional arguments: expected ${positionalParams.length}, got ${positionalArgs.length}', + 'Invalid number of positional arguments: expected ${binding.positionalParams.length}, got ${positionalArgs.length}', ); } // Check if the types of positional arguments match - for (int i = 0; i < positionalParams.length; i++) { + for (int i = 0; i < binding.positionalParams.length; i++) { if (i >= positionalArgs.length) { throw ArgumentError( - '${positionalParams.length} positional arguments expected, but only ${positionalArgs.length} provided', + '${binding.positionalParams.length} positional arguments expected, but only ${positionalArgs.length} provided', ); } final type = $Type.fromValue(positionalArgs[i]); - if (!type.canCast(positionalParams[i])) { + if (!type.canCast(binding.positionalParams.values.elementAt(i))) { throw ArgumentError( - 'Invalid argument type for positional argument $i: expected ${positionalParams[i]}, got $type', + 'Invalid argument type for positional argument $i: expected ${binding.positionalParams.values.elementAt(i)}, got $type', ); } // Cast the argument to the expected type - positionalArgs[i] = type.cast(positionalParams[i], positionalArgs[i]); - } - - final result = await Function.apply( - function, - positionalArgs, - namedArgs, - ); - - final resultType = $Type.fromValue(result); - - if (resultType.canCast(returnType)) { - // Cast the result to the expected return type - return resultType.cast(returnType, result) as T; - } else { - throw ArgumentError( - 'Invalid return type: expected $T, got ${result.runtimeType}', + positionalArgs[i] = type.cast( + binding.positionalParams.values.elementAt(i), + positionalArgs[i], ); } } @@ -159,12 +233,16 @@ class ExternalBindings extends LibraryBinding { ExternalBindings() : super( name: 'external', + description: + 'Library for external bindings defined by the host at runtime.', ); /// Creates an [ExternalBindings] instance with the provided list of bindings. ExternalBindings.from(List bindings) : super( name: 'external', + description: + 'Library for external bindings defined by the host at runtime.', ) { _bindings.addAll(bindings); } @@ -182,3 +260,32 @@ class ExternalBindings extends LibraryBinding { @override Set get bindings => _bindings; } + +/// A callback that is called before a binding's function is called. +/// +/// It can be used to modify the binding or the arguments before the function is called. +/// +/// This will block the execution of the binding's function until it completes, so no heavy operations should be done here, +/// as it will slow down the Dscript runtime. +/// +/// If this function throws an error, the binding will rethrow the error, canceling its execution. +typedef PreBindingMiddleware = FutureOr Function({ + required RuntimeBinding binding, + required List positionalArgs, + required Map namedArgs, +}); + +/// A callback that is called after a binding's function is called. +/// +/// It can be used to modify the result of the binding or perform additional actions after the function is called. +/// +/// This will block the execution of the binding's function until it completes, so no heavy operations should be done here, +/// as it will slow down the Dscript runtime. +/// +/// If this function throws an error, the binding will rethrow the error, canceling its execution. +typedef PostBindingMiddleware = FutureOr Function({ + required RuntimeBinding binding, + required T result, + required List positionalArgs, + required Map namedArgs, +}); diff --git a/lib/src/compiler/instructions.dart b/lib/src/compiler/instructions.dart index 2e90c8b..1736c6e 100644 --- a/lib/src/compiler/instructions.dart +++ b/lib/src/compiler/instructions.dart @@ -395,7 +395,7 @@ class Instruction { /// 1. Elements (dynamic) Γ— count. /// /// **Results** - /// 1. List. + /// 1. `List`. /// /// {@macro instruction} static const array = 0x17; @@ -411,7 +411,7 @@ class Instruction { /// 1. Values (dynamic) Γ— (count*2) [key, value]. /// /// **Results** - /// 1. Map. + /// 1. `Map`. /// /// {@macro instruction} static const map = 0x18; @@ -557,7 +557,7 @@ class Instruction { /// 1. Constant pool index containing the struct's name. /// /// **Operands** - /// 1. A Map. + /// 1. A `Map`. /// /// **Results** /// 1. Struct instance. @@ -574,8 +574,8 @@ class Instruction { /// 2. Constant pool index for the function name. /// /// **Operands** - /// 1. Positional arguments as List or null ([Instruction.pushNull]). - /// 2. Named arguments as Map or null ([Instruction.pushNull]). + /// 1. Positional arguments as `List` or null ([Instruction.pushNull]). + /// 2. Named arguments as `Map` or null ([Instruction.pushNull]). /// /// **Results** /// 1. Return value (dynamic). @@ -659,8 +659,8 @@ class Instruction { /// 1. Constant pool index for the function name. /// /// **Operands** - /// 1. Positional arguments as List or null ([Instruction.pushNull]). - /// 2. Named arguments as Map or null ([Instruction.pushNull]). + /// 1. Positional arguments as `List` or null ([Instruction.pushNull]). + /// 2. Named arguments as `Map` or null ([Instruction.pushNull]). static const call = 0x27; /// Begins a try block and registers the catch handler target. diff --git a/lib/src/compiler/naivie_compiler.dart b/lib/src/compiler/naivie_compiler.dart index 63b155f..b6069f2 100644 --- a/lib/src/compiler/naivie_compiler.dart +++ b/lib/src/compiler/naivie_compiler.dart @@ -236,10 +236,21 @@ class NaiveCompiler extends Compiler { startLoop(); - // Condition: index < iterable.length - emit(Instruction.read, indexTemp.frame, indexTemp.index); + // push iter on the stack. emit(Instruction.read, iterableTemp.frame, iterableTemp.index); - emit(Instruction.readProperty, addConstant('length')); + + // Prepare arguments for length call. + emit(Instruction.array, 1); + emit(Instruction.pushNull); + + // call dynamic::length(iterable) + final ns = addConstant(const DynamicBindings().name); + final method = addConstant(DynamicBindings.lengthBinding.name); + emit(Instruction.externalCall, ns, method); + + emit(Instruction.read, indexTemp.frame, indexTemp.index); + + // Condition: index < iterable.length emit(Instruction.lt); final idx = prepareJump(Instruction.jumpIfFalse); @@ -620,6 +631,7 @@ class NaiveCompiler extends Compiler { ctx.throwStmt()?.accept(this); ctx.tryStmt()?.accept(this); ctx.throwStmt()?.accept(this); + ctx.assignment()?.accept(this); } @override diff --git a/lib/src/contract_builder.dart b/lib/src/contract_builder.dart index f83ec5f..927f3ef 100644 --- a/lib/src/contract_builder.dart +++ b/lib/src/contract_builder.dart @@ -31,8 +31,8 @@ class ContractSignatureBuilder { /// Adds a new struct definition with the specified [name], returning /// a [StructBuilder] to configure fields and description. - StructBuilder struct(String name) { - final builder = StructBuilder(name, this); + StructBuilder struct(String name) { + final builder = StructBuilder(name, this); return builder; } @@ -90,8 +90,9 @@ class BindingBuilder { final String _name; final List _permissions = []; final Function _function; - final List<$Type> _params = []; + final Map _params = {}; final Map _namedParams = {}; + $Type? _returnType; String _description = ''; /// Internal constructor; typically obtained via [ContractSignatureBuilder.bind]. @@ -104,8 +105,8 @@ class BindingBuilder { } /// Adds a positional parameter of the given [type]. - BindingBuilder param($Type type) { - _params.add(type); + BindingBuilder param(String name, $Type type) { + _params[name] = type; return this; } @@ -124,6 +125,12 @@ class BindingBuilder { return this; } + /// Sets the return type of the binding. + BindingBuilder returns($Type type) { + _returnType = type; + return this; + } + /// Completes this binding and adds it back to the parent builder, /// returning the parent [ContractSignatureBuilder]. ContractSignatureBuilder end() { @@ -137,13 +144,19 @@ class BindingBuilder { /// Builds the immutable [RuntimeBinding] instance. RuntimeBinding build() { + if (_returnType == null) { + throw StateError( + 'Return type must be set before building the binding. Call `returns`'); + } + return RuntimeBinding( name: _name, function: _function, permissions: _permissions, - positionalParams: List.unmodifiable(_params), + positionalParams: Map.unmodifiable(_params), namedParams: Map.unmodifiable(_namedParams), description: _description.isNotEmpty ? _description : null, + returnType: _returnType!, ); } } @@ -252,12 +265,15 @@ class HookBuilder { /// /// Call [field] to add fields, [describe] to add documentation, /// then [end] to attach it to its parent contract. -class StructBuilder { +class StructBuilder { final String _name; final Map _fields = {}; String _description = ''; final ContractSignatureBuilder? _parent; + T Function(Map)? _toDart; + Map Function(T)? _fromDart; + /// Creates a struct builder with the given [name]. StructBuilder(this._name, this._parent); @@ -276,6 +292,18 @@ class StructBuilder { return this; } + /// See [Struct.toDart] + StructBuilder toDart(T Function(Map) toDart) { + _toDart = toDart; + return this; + } + + /// See [Struct.fromDart] + StructBuilder fromDart(Map Function(T) fromDart) { + _fromDart = fromDart; + return this; + } + /// Completes this struct and adds it to the parent, /// returning the parent [ContractSignatureBuilder]. ContractSignatureBuilder end() { @@ -289,10 +317,21 @@ class StructBuilder { /// Builds the immutable [Struct] definition. Struct build() { - return Struct( + if (_fields.isEmpty) { + throw StateError('Struct must have at least one field defined.'); + } + + if (_toDart == null || _fromDart == null) { + throw StateError( + 'Struct must have both toDart and fromDart functions defined.'); + } + + return Struct( name: _name, fields: Map.unmodifiable(_fields), description: _description.isNotEmpty ? _description : null, + toDart: _toDart!, + fromDart: _fromDart!, ); } } @@ -302,7 +341,7 @@ ContractSignatureBuilder contract(String name) => ContractSignatureBuilder(name); /// Shorthand to start a standalone [StructBuilder]. -StructBuilder struct(String name) => StructBuilder(name, null); +StructBuilder struct(String name) => StructBuilder(name, null); /// Shorthand to start a standalone [HookBuilder]. HookBuilder hook(String name) => HookBuilder(null, name); diff --git a/lib/src/permissions.dart b/lib/src/permissions.dart index 5642ab1..1d26c67 100644 --- a/lib/src/permissions.dart +++ b/lib/src/permissions.dart @@ -18,9 +18,8 @@ class ScriptPermission { /// Creates a custom host-defined permission with the `external` namespace. const ScriptPermission.custom(this.name) : namespace = 'external'; - @override - /// Returns the DSL-style string representation `namespace::method`. + @override String toString() => '$namespace::$name'; /// Permission to read from the filesystem (`fs::read`). @@ -29,11 +28,8 @@ class ScriptPermission { /// Permission to write to the filesystem (`fs::write`). static const writeFiles = ScriptPermission('fs', 'write'); - /// Permission to perform network client requests (`ntwk::client`). - static const networkClient = ScriptPermission('http', 'client'); - - /// Permission to start a network server (`ntwk::server`). - static const networkServer = ScriptPermission('http', 'server'); + /// Permission to perform network client requests (`http::client`). + static const httpClient = ScriptPermission('http', 'client'); @override bool operator ==(Object other) { diff --git a/lib/src/runtime/runtime.dart b/lib/src/runtime/runtime.dart index 400e7d1..024c808 100644 --- a/lib/src/runtime/runtime.dart +++ b/lib/src/runtime/runtime.dart @@ -1,7 +1,6 @@ import 'dart:isolate'; import 'package:dscript_dart/dscript_dart.dart'; -import 'package:dscript_dart/src/stdlib/stdlib.dart'; /// The Dscript runtime environment. /// diff --git a/lib/src/stdlib/base64.dart b/lib/src/stdlib/base64.dart new file mode 100644 index 0000000..219e579 --- /dev/null +++ b/lib/src/stdlib/base64.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:dscript_annotations/dscript_annotations.dart'; +import 'package:dscript_dart/dscript_dart.dart'; + +part 'base64.g.dart'; + +/// Provides utility functions for Base64 encoding and decoding. +@namespace +class Base64Bindings extends _$Base64Bindings { + /// Provides utility functions for Base64 encoding and decoding. + const Base64Bindings(); + + /// Encodes [input] to Base64 format. + @override + String encode(String input) { + return base64.encode(input.codeUnits); + } + + /// Decodes [input] from Base64 format. + @override + String decode(String input) { + return utf8.decode(base64.decode(input)); + } +} diff --git a/lib/src/stdlib/base64.g.dart b/lib/src/stdlib/base64.g.dart new file mode 100644 index 0000000..6328e86 --- /dev/null +++ b/lib/src/stdlib/base64.g.dart @@ -0,0 +1,106 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'base64.dart'; + +// ************************************************************************** +// Generator: DscriptNamespace +// ************************************************************************** + +abstract class _$Base64Bindings extends LibraryBinding { + const _$Base64Bindings() + : super( + name: 'base64', + description: + 'Provides utility functions for Base64 encoding and decoding.'); + + @override + Set get bindings => {encodeBinding, decodeBinding}; + + static final List> _encodePostMiddlewares = []; + static final List> _encodePreMiddlewares = []; + + bool _isencodeBinding(RuntimeBinding binding) { + return binding.name == 'encode' && + binding.description == encodeBinding.description && + binding.returnType == encodeBinding.returnType && + binding.positionalParams == encodeBinding.positionalParams && + binding.namedParams == encodeBinding.namedParams && + binding.permissions == encodeBinding.permissions; + } + + /// Encodes [input] to Base64 format. + String encode(String input); + + /// Binding for [encode]. + RuntimeBinding get encodeBinding => RuntimeBinding( + name: 'encode', + description: 'Encodes [input] to Base64 format.', + function: encode, + returnType: PrimitiveType.STRING, + positionalParams: {'input': PrimitiveType.STRING}, + namedParams: {}, + permissions: const [], + preMiddlewares: _encodePreMiddlewares, + postMiddlewares: _encodePostMiddlewares, + ); + + static final List> _decodePostMiddlewares = []; + static final List> _decodePreMiddlewares = []; + + bool _isdecodeBinding(RuntimeBinding binding) { + return binding.name == 'decode' && + binding.description == decodeBinding.description && + binding.returnType == decodeBinding.returnType && + binding.positionalParams == decodeBinding.positionalParams && + binding.namedParams == decodeBinding.namedParams && + binding.permissions == decodeBinding.permissions; + } + + /// Decodes [input] from Base64 format. + String decode(String input); + + /// Binding for [decode]. + RuntimeBinding get decodeBinding => RuntimeBinding( + name: 'decode', + description: 'Decodes [input] from Base64 format.', + function: decode, + returnType: PrimitiveType.STRING, + positionalParams: {'input': PrimitiveType.STRING}, + namedParams: {}, + permissions: const [], + preMiddlewares: _decodePreMiddlewares, + postMiddlewares: _decodePostMiddlewares, + ); + + /// Registers global middlewares for a [RuntimeBinding]. + /// + /// These middlewares will always run when a binding is called, regardless of the context. + /// + /// For context aware middlewares you can call the [RuntimeBinding.addPreBindingMiddleware] and [RuntimeBinding.addPostBindingMiddleware] methods on the binding directly. + void registerGlobalMiddlewares(RuntimeBinding binding, + {List> preMiddlewares = const [], + List> postMiddlewares = const []}) { + if (_isencodeBinding(binding)) { + _encodePreMiddlewares + .addAll(preMiddlewares as List>); + _encodePostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isdecodeBinding(binding)) { + _decodePreMiddlewares + .addAll(preMiddlewares as List>); + _decodePostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + // If no binding matched, throw an error. + + throw ArgumentError.value(binding, 'binding', + 'Binding does not match any known bindings in base64: String, String'); + } +} diff --git a/lib/src/stdlib/date_time.dart b/lib/src/stdlib/date_time.dart new file mode 100644 index 0000000..0c3f504 --- /dev/null +++ b/lib/src/stdlib/date_time.dart @@ -0,0 +1,35 @@ +import 'package:dscript_dart/dscript_dart.dart'; + +/// Bindings for [DateTime] class. +class DateTimeBindings extends LibraryBinding { + /// Bindings for [DateTime] class. + DateTimeBindings({required super.name, required super.description}); + + @override + Set get bindings => { + nowBinding, + }; + + /// Binding for [DateTime.now]. + static final nowBinding = RuntimeBinding( + name: 'now', + description: 'Returns the current date and time.', + function: () => Struct.dateTime.fromDart(DateTime.now()), + returnType: Struct.dateTime, + ); + + /// Binding for [DateTime.subtract]. + static final subtractBinding = RuntimeBinding( + name: 'subtract', + description: + 'Subtracts the specified duration from the current date and time.', + function: (DateTime datetime, Duration duration) { + return Struct.dateTime.fromDart(datetime.subtract(duration)); + }, + positionalParams: { + 'datetime': Struct.dateTime, + 'duration': Struct.duration, + }, + returnType: Struct.dateTime, + ); +} diff --git a/lib/src/stdlib/dynamic.dart b/lib/src/stdlib/dynamic.dart new file mode 100644 index 0000000..8591499 --- /dev/null +++ b/lib/src/stdlib/dynamic.dart @@ -0,0 +1,101 @@ +part of 'stdlib.dart'; + +/// Bindings for the dynamic standard library. +class DynamicBindings extends LibraryBinding { + /// Bindings for the dynamic standard library. + const DynamicBindings() + : super( + name: 'dynamic', + description: 'Various utilities for dynamic values.', + ); + + @override + Set get bindings => { + toStringBinding, + toIntBinding, + toDoubleBinding, + toBoolBinding, + lengthBinding, + typeBinding, + }; + + /// Binding for converting a dynamic value to a string. + static final toStringBinding = RuntimeBinding( + name: 'toString', + function: (dynamic value) => value.toString(), + positionalParams: { + 'value': const DynamicType(), + }, + description: 'Converts [value] to a string.', + returnType: PrimitiveType.STRING, + ); + + /// Binding for converting a dynamic value to an int. + static final toIntBinding = RuntimeBinding( + name: 'toInt', + function: (dynamic value) => + value is int ? value : int.parse(value.toString()), + positionalParams: { + 'value': const DynamicType(), + }, + description: 'Converts [value] to an int.', + returnType: PrimitiveType.INT, + ); + + /// Binding for converting a dynamic value to a double. + static final toDoubleBinding = RuntimeBinding( + name: 'toDouble', + function: (dynamic value) => + value is double ? value : double.parse(value.toString()), + positionalParams: { + 'value': const DynamicType(), + }, + description: 'Converts [value] to a double.', + returnType: PrimitiveType.DOUBLE, + ); + + /// Binding for converting a dynamic value to a bool. + static final toBoolBinding = RuntimeBinding( + name: 'toBool', + function: (dynamic value) => + value is bool ? value : (value.toString().toLowerCase() == 'true'), + positionalParams: { + 'value': const DynamicType(), + }, + description: 'Converts [value] to a bool.', + returnType: PrimitiveType.BOOL, + ); + + /// Returns the length of a dynamic value if it is a collection or string, otherwise returns 1. + static final lengthBinding = RuntimeBinding( + name: 'length', + function: (dynamic value) { + if (value is String || value is List || value is Map) { + return value.length; + } + + throw ArgumentError.value( + value, + 'value', + 'Value must be a collection or string to get length.', + ); + }, + positionalParams: { + 'value': const DynamicType(), + }, + description: + 'Returns the length of [value] if it is a collection or string, otherwise throws.', + returnType: PrimitiveType.INT, + ); + + /// Returns the type of the dynamic value as a string. + static final typeBinding = RuntimeBinding( + name: 'type', + function: (dynamic value) => $Type.fromValue(value).toString(), + positionalParams: { + 'value': const DynamicType(), + }, + description: 'Returns the type of [value] as a string.', + returnType: PrimitiveType.STRING, + ); +} diff --git a/lib/src/stdlib/fs.dart b/lib/src/stdlib/fs.dart index 69b946b..460f748 100644 --- a/lib/src/stdlib/fs.dart +++ b/lib/src/stdlib/fs.dart @@ -1 +1,314 @@ part of 'stdlib.dart'; + +/// Bindings for the file system standard library. +class FsBindings extends LibraryBinding { + /// Bindings for the file system standard library. + const FsBindings() + : super( + name: 'fs', + description: 'Library for interacting with the file system.', + ); + + @override + Set get bindings => { + readFileBinding, + writeFileBinding, + deleteFileBinding, + fileExistsBinding, + listDirectoryBinding, + createDirectoryBinding, + isDirectoryBinding, + isFileBinding, + absolutePathBinding, + currentDirectoryBinding, + moveBinding, + copyBinding, + fileSizeBinding, + extensionBinding, + basenameBinding, + dirnameBinding, + }; + + /// Reads the contents of a file as a string. + static final readFileBinding = RuntimeBinding( + name: 'read', + function: (String path) async { + final file = File(path); + if (!file.existsSync()) { + throw FileSystemException('File not found', path); + } + return await file.readAsString(); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: 'Reads the contents of a file at the given path as a string.', + returnType: PrimitiveType.STRING, + ); + + /// Writes a string to a file. + static final writeFileBinding = RuntimeBinding( + name: 'write', + function: ( + {required String path, required String content, bool? append}) async { + final file = File(path); + append ??= false; // Default to false if not provided + await file.writeAsString(content, + mode: append ? FileMode.append : FileMode.write); + }, + namedParams: { + #append: PrimitiveType.BOOL.asNullable(), + #path: PrimitiveType.STRING, + #content: PrimitiveType.STRING, + }, + permissions: [ScriptPermission.writeFiles], + description: + 'Writes a string to a file at the given [path]. If [append] is true, it appends to the file; otherwise, it overwrites the file. The file is created if it does not exist.', + returnType: PrimitiveType.VOID, + ); + + /// Deletes a file at the given path. + static final deleteFileBinding = RuntimeBinding( + name: 'delete', + function: (String path) async { + final file = File(path); + if (!file.existsSync()) { + throw FileSystemException('File not found', path); + } + + await file.delete(); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.writeFiles], + description: 'Deletes a file at the given [path].', + returnType: PrimitiveType.VOID, + ); + + /// Checks if a file exists at the given path. + static final fileExistsBinding = RuntimeBinding( + name: 'exists', + function: (String path) async { + final file = File(path); + return await file.exists(); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: 'Checks if a file exists at the given [path].', + returnType: PrimitiveType.BOOL, + ); + + /// Lists the contents of a directory. + static final listDirectoryBinding = RuntimeBinding>( + name: 'ls', + function: (String path) async { + final dir = Directory(path); + if (!dir.existsSync()) { + throw FileSystemException('Directory not found', path); + } + final contents = dir.listSync(); + return contents + .whereType() + .map((file) => file.path) + .toList(growable: false); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: + 'Lists the contents of a directory at the given [path]. Returns a list of file paths.', + returnType: ListType(elementType: PrimitiveType.STRING), + ); + + /// Creates a directory at the given path. + static final createDirectoryBinding = RuntimeBinding( + name: 'mkdir', + function: (String path) async { + final dir = Directory(path); + if (dir.existsSync()) { + return; + } + await dir.create(recursive: true); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.writeFiles], + description: + 'Creates a directory at the given [path]. If the directory already exists, it does nothing.', + returnType: PrimitiveType.VOID, + ); + + /// Checks if the path is a directory. + static final isDirectoryBinding = RuntimeBinding( + name: 'isDir', + function: (String path) async { + return await FileSystemEntity.isDirectory(path); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: 'Checks if the path is a directory.', + returnType: PrimitiveType.BOOL, + ); + + /// Checks if the path is a file. + static final isFileBinding = RuntimeBinding( + name: 'isFile', + function: (String path) async { + return await FileSystemEntity.isFile(path); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: 'Checks if the path is a file.', + returnType: PrimitiveType.BOOL, + ); + + /// Gets the absolute path of a file or directory. + static final absolutePathBinding = RuntimeBinding( + name: 'absolute', + function: (String path) { + final entity = File(path); + return entity.absolute.path; + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: + 'Gets the absolute path of a file or directory at the given [path].', + returnType: PrimitiveType.STRING, + ); + + /// Returns the current working directory. + static final currentDirectoryBinding = RuntimeBinding( + name: 'cwd', + function: () => Directory.current.path, + permissions: [ScriptPermission.readFiles], + description: 'Returns the current working directory.', + returnType: PrimitiveType.STRING, + ); + + /// Moves a file or directory to a new location. + static final moveBinding = RuntimeBinding( + name: 'move', + function: ({required String from, required String to}) async { + final source = File(from); + if (!source.existsSync()) { + throw FileSystemException('Source file not found', from); + } + await source.rename(to); + }, + namedParams: { + #from: PrimitiveType.STRING, + #to: PrimitiveType.STRING, + }, + permissions: [ScriptPermission.writeFiles, ScriptPermission.readFiles], + description: 'Moves a file or directory from [from] to [to].', + returnType: PrimitiveType.VOID, + ); + + /// Copies a file or directory to a new location. + static final copyBinding = RuntimeBinding( + name: 'copy', + function: ({required String from, required String to}) async { + final source = File(from); + if (!source.existsSync()) { + throw FileSystemException('Source file not found', from); + } + await source.copy(to); + }, + namedParams: { + #from: PrimitiveType.STRING, + #to: PrimitiveType.STRING, + }, + permissions: [ScriptPermission.writeFiles, ScriptPermission.readFiles], + description: 'Copies a file or directory from [from] to [to].', + returnType: PrimitiveType.VOID, + ); + + /// Gets the size of a file in bytes. + static final fileSizeBinding = RuntimeBinding( + name: 'size', + function: (String path) async { + final file = File(path); + if (!file.existsSync()) { + throw FileSystemException('File not found', path); + } + return await file.length(); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], + description: 'Gets the size of a file at the given [path] in bytes.', + returnType: PrimitiveType.INT, + ); + + /// Gets the file extension of a file. + static final extensionBinding = RuntimeBinding( + name: 'extension', + function: (String path) { + final file = File(path); + + if (!path.contains('.')) { + return ''; + } + + return file.uri.pathSegments.last.split('.').last; + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + description: + 'Gets the file extension of the given [path]. Returns an empty string if no extension is found.', + returnType: PrimitiveType.STRING, + ); + + /// Gets the base name of a file or directory. + static final basenameBinding = RuntimeBinding( + name: 'basename', + function: (String path) { + final file = File(path); + final name = file.uri.pathSegments.last; + + if (name.contains('.')) { + return name.split('.').first; + } + + return name; + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + description: 'Gets the base name of the given [path].', + returnType: PrimitiveType.STRING, + ); + + /// Gets the directory name of a file or directory. + static final dirnameBinding = RuntimeBinding( + name: 'dirname', + function: (String path) { + return FileSystemEntity.parentOf(path); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + description: r""" +The parent path of a path. + +Finds the final path component of a path, using the platform's path separator to split the path, and returns the prefix up to that part. + +Will not remove the root component of a Windows path, like "C:\" or "\\server_name\". Includes a trailing path separator in the last part of [path], and leaves no trailing path separator. +""", + returnType: PrimitiveType.STRING, + ); +} diff --git a/lib/src/stdlib/http.dart b/lib/src/stdlib/http.dart new file mode 100644 index 0000000..8ae1188 --- /dev/null +++ b/lib/src/stdlib/http.dart @@ -0,0 +1,308 @@ +part of 'stdlib.dart'; + +/// Bindings for the HTTP standard library. +class HttpBindings extends LibraryBinding { + /// The Dio instance used for making HTTP requests. + /// + /// You can override this instance to customize the HTTP client, for example, to set a base URL or interceptors. + static Dio dio = Dio(BaseOptions( + validateStatus: (status) => true, // Accept all status codes + )); + + /// Bindings for the HTTP standard library. + const HttpBindings() + : super( + name: 'http', + description: 'Library for making HTTP requests.', + ); + + @override + Set get bindings => { + getBinding, + postBinding, + putBinding, + deleteBinding, + patchBinding, + headBinding, + optionsBinding, + }; + + /// Binding for making a GET request. + static final getBinding = RuntimeBinding( + name: 'get', + function: ( + String path, { + Map? queryParams, + Map? headers, + }) async { + final response = await dio.get( + path, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a GET request to the specified [path] with optional query parameters and headers.', + ); + + /// Binding for making a POST request. + static final postBinding = RuntimeBinding( + name: 'post', + function: ( + String path, { + Map? data, + Map? queryParams, + Map? headers, + }) async { + final response = await dio.post( + path, + data: data, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #data: const DynamicType(), + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a POST request to the specified [path] with optional data, query parameters, and headers.', + ); + + /// Binding for making a PUT request. + static final putBinding = RuntimeBinding( + name: 'put', + function: ( + String path, { + Map? data, + Map? queryParams, + Map? headers, + }) async { + final response = await dio.put( + path, + data: data, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #data: const DynamicType(), + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a PUT request to the specified [path] with optional data, query parameters, and headers.', + ); + + /// Binding for making a PATCH request. + static final patchBinding = RuntimeBinding( + name: 'patch', + function: ( + String path, { + Map? data, + Map? queryParams, + Map? headers, + }) async { + final response = await dio.patch( + path, + data: data, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #data: const DynamicType(), + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a PATCH request to the specified [path] with optional data, query parameters, and headers.', + ); + + /// Binding for making a DELETE request. + static final deleteBinding = RuntimeBinding( + name: 'delete', + function: ( + String path, { + Map? data, + Map? queryParams, + Map? headers, + }) async { + final response = await dio.delete( + path, + data: data, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #data: const DynamicType(), + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a DELETE request to the specified [path] with optional data, query parameters, and headers.', + ); + + /// Binding for making a HEAD request. + static final headBinding = RuntimeBinding( + name: 'head', + function: ( + String path, { + Map? queryParams, + Map? headers, + }) async { + final response = await dio.head( + path, + queryParameters: queryParams, + options: Options( + headers: headers, + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes a HEAD request to the specified [path] with optional query parameters and headers.', + ); + + /// Binding for making an OPTIONS request. + static final optionsBinding = RuntimeBinding( + name: 'options', + function: ( + String path, { + Map? queryParams, + Map? headers, + }) async { + final response = await dio.get( + path, + queryParameters: queryParams, + options: Options( + headers: headers, + method: 'OPTIONS', // Set the method to OPTIONS + ), + ); + return Struct.httpResponse.fromDart(response); + }, + positionalParams: { + 'path': PrimitiveType.STRING, + }, + namedParams: { + #queryParams: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + #headers: MapType( + keyType: PrimitiveType.STRING, + valueType: const DynamicType(), + ).asNullable(), + }, + returnType: Struct.httpResponse, + permissions: [ + ScriptPermission.httpClient, + ], + description: + 'Makes an OPTIONS request to the specified [path] with optional query parameters and headers.', + ); +} diff --git a/lib/src/stdlib/json.dart b/lib/src/stdlib/json.dart new file mode 100644 index 0000000..c745e8e --- /dev/null +++ b/lib/src/stdlib/json.dart @@ -0,0 +1,48 @@ +part of 'stdlib.dart'; + +/// Provides JSON encoding and decoding functions. +class JsonBindings extends LibraryBinding { + /// Provides JSON encoding and decoding functions. + const JsonBindings() + : super( + name: 'json', + description: 'Provides JSON encoding and decoding functions.', + ); + + @override + Set get bindings => { + decodeBinding, + encodeBinding, + }; + + /// Binding for [jsonDecode]. + static final decodeBinding = RuntimeBinding( + name: 'decode', + description: '''Deserializes the given [str] to a [JSON] object. + + Throws an error if the string is not valid JSON or cannot be parsed. + ''', + function: (String str) { + final json = jsonDecode(str); + + return Struct.json.fromDart(json); + }, + returnType: Struct.json, + positionalParams: {'str': PrimitiveType.STRING}, + ); + + /// Binding for [jsonEncode]. + static final encodeBinding = RuntimeBinding( + name: 'encode', + description: '''Serializes the given [obj] to a JSON string. + + Throws an error if the object cannot be serialized.''', + function: (dynamic obj) { + return jsonEncode(obj); + }, + positionalParams: { + 'obj': const DynamicType(), + }, + returnType: PrimitiveType.STRING, + ); +} diff --git a/lib/src/stdlib/list.dart b/lib/src/stdlib/list.dart new file mode 100644 index 0000000..acd786f --- /dev/null +++ b/lib/src/stdlib/list.dart @@ -0,0 +1,217 @@ +part of 'stdlib.dart'; + +/// Bindings for the list standard library. +class ListBindings extends LibraryBinding { + /// Bindings for the list standard library. + const ListBindings() + : super( + name: 'list', + description: 'Library for working with lists.', + ); + + @override + Set get bindings => { + lengthBinding, + isEmptyBinding, + isNotEmptyBinding, + addBinding, + addAllBinding, + clearBinding, + removeBinding, + removeAtBinding, + removeLastBinding, + insertBinding, + insertAllBinding, + indexOfBinding, + lastIndexOfBinding, + containsBinding, + copyBinding, + }; + + /// Binding for [List.length]. + static final lengthBinding = RuntimeBinding( + name: 'length', + function: (List list) => list.length, + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Returns the number of elements in the list.', + returnType: PrimitiveType.INT, + ); + + /// Binding for [List.isEmpty]. + static final isEmptyBinding = RuntimeBinding( + name: 'isEmpty', + function: (List list) => list.isEmpty, + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Returns true if the list is empty.', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [List.isNotEmpty]. + static final isNotEmptyBinding = RuntimeBinding( + name: 'isNotEmpty', + function: (List list) => list.isNotEmpty, + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Returns true if the list is not empty.', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [List.add]. + static final addBinding = RuntimeBinding( + name: 'add', + function: (List list, dynamic element) => list.add(element), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Adds [element] to the end of the [list].', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [List.addAll]. + static final addAllBinding = RuntimeBinding( + name: 'addAll', + function: (List list, List elements) => + list.addAll(elements), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'elements': ListType(elementType: const DynamicType()), + }, + description: 'Adds all [elements] to the end of the [list].', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [List.clear]. + static final clearBinding = RuntimeBinding( + name: 'clear', + function: (List list) => list.clear(), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Removes all elements from the list.', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [List.remove]. + static final removeBinding = RuntimeBinding( + name: 'remove', + function: (List list, dynamic element) => list.remove(element), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Removes the first occurrence of [element] from the list.', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [List.removeAt]. + static final removeAtBinding = RuntimeBinding( + name: 'removeAt', + function: (List list, int index) => list.removeAt(index), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + }, + description: 'Removes and returns the element at [index] from the list.', + returnType: const DynamicType(), + ); + + /// Binding for [List.removeLast]. + static final removeLastBinding = RuntimeBinding( + name: 'removeLast', + function: (List list) => list.removeLast(), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Removes and returns the last element from the list.', + returnType: const DynamicType(), + ); + + /// Binding for [List.insert]. + static final insertBinding = RuntimeBinding( + name: 'insert', + function: (List list, int index, dynamic element) => + list.insert(index, element), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + 'element': const DynamicType(), + }, + description: 'Inserts [element] at [index] in the list.', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [List.insertAll]. + static final insertAllBinding = RuntimeBinding( + name: 'insertAll', + function: (List list, int index, List elements) => + list.insertAll(index, elements), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + 'elements': ListType(elementType: const DynamicType()), + }, + description: 'Inserts all [elements] at [index] in the list.', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [List.indexOf]. + static final indexOfBinding = RuntimeBinding( + name: 'indexOf', + function: (List list, dynamic element, {int? start}) => + list.indexOf(element, start ?? 0), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, + namedParams: { + #start: PrimitiveType.INT.asNullable(), + }, + description: + 'Returns the index of the first occurrence of [element] in the list, starting from [start].', + returnType: PrimitiveType.INT, + ); + + /// Binding for [List.lastIndexOf]. + static final lastIndexOfBinding = RuntimeBinding( + name: 'lastIndexOf', + function: (List list, dynamic element, {int? start}) => + list.lastIndexOf(element, start), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, + namedParams: { + #start: PrimitiveType.INT.asNullable(), + }, + description: + 'Returns the index of the last occurrence of [element] in the list, starting from [start].', + returnType: PrimitiveType.INT, + ); + + /// Binding for [List.contains]. + static final containsBinding = RuntimeBinding( + name: 'contains', + function: (List list, dynamic element) => list.contains(element), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, + description: 'Returns true if the list contains [element].', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [List.from]. + static final copyBinding = RuntimeBinding>( + name: 'copy', + function: (List list) => List.from(list), + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Returns a copy of the [list].', + returnType: ListType(elementType: const DynamicType()), + ); +} diff --git a/lib/src/stdlib/log.dart b/lib/src/stdlib/log.dart index 4a9e02f..7dda138 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -1,123 +1,76 @@ // coverage:ignore-file -part of 'stdlib.dart'; -/// Bindings for the logging standard library. -class LogBindings extends LibraryBinding { +import 'package:dscript_annotations/dscript_annotations.dart'; +import 'package:logging/logging.dart'; +import 'package:dscript_dart/dscript_dart.dart'; + +part 'log.g.dart'; + +/// Provides basic logging utitlities. +/// +/// !> Log messages will not be visible when using [IsolateRuntime]. +@namespace +class LogBindings extends _$LogBindings { /// The logger instance for this script. late final Logger logger; /// Bindings for the logging standard library. - LogBindings(ScriptMetadata metadata) : super(name: 'log') { + LogBindings(ScriptMetadata metadata) { logger = Logger( '[Dscript] ${metadata.author}.${metadata.name}@${metadata.version}', ); } + /// Logs general informational messages that highlight the progress of the application at a coarse level. @override - Set get bindings => { - infoBinding, - warningBinding, - errorBinding, - debugBinding, - verboseBinding, - fatalBinding, - criticalBinding, - }; + void info(dynamic message, {dynamic error}) { + logger.info(message, error); + } - /// Binding for info logging. - late final infoBinding = RuntimeBinding( - name: 'info', - function: (dynamic message, {dynamic error}) => logger.info(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs an info message.', - ); + /// Logs potential problems that are not yet errors but might require attention or could lead to issues. + @override + void warning(dynamic message, {dynamic error}) { + logger.warning(message, error); + } - /// Binding for warning logging. - late final warningBinding = RuntimeBinding( - name: 'warning', - function: (dynamic message, {dynamic error}) => - logger.warning(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a warning message.', - ); + /// Logs serious failures or errors that will likely prevent normal program execution. + @override + void severe(dynamic message, {dynamic error}) { + logger.severe(message, error); + } - /// Binding for error logging. - late final errorBinding = RuntimeBinding( - name: 'error', - function: (dynamic message, {dynamic error}) { - logger.severe(message, error); - }, - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs an error message.', - ); + /// Logs tracing information for debugging purposes. + /// Less verbose than [finer] or [finest]. + @override + void fine(dynamic message, {dynamic error}) { + logger.fine(message, error); + } - /// Binding for debug logging. - late final debugBinding = RuntimeBinding( - name: 'debug', - function: (dynamic message, {dynamic error}) => logger.fine(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a debug message.', - ); + /// Logs fairly detailed tracing information. + /// Useful when debugging complex flows with more granularity than [fine]. + @override + void finer(dynamic message, {dynamic error}) { + logger.finer(message, error); + } - /// Binding for verbose logging. - late final verboseBinding = RuntimeBinding( - name: 'verbose', - function: (dynamic message, {dynamic error}) => - logger.finer(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a verbose message.', - ); + /// Logs highly detailed tracing information. + /// Intended for deep debugging, usually too verbose for normal use. + @override + void finest(dynamic message, {dynamic error}) { + logger.finest(message, error); + } - /// Binding for fatal logging. - late final fatalBinding = RuntimeBinding( - name: 'fatal', - function: (dynamic message, {dynamic error}) => - logger.shout(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a fatal message.', - ); + /// Logs static configuration messages. + /// Typically used to record startup settings or environment details. + @override + void config(dynamic message, {dynamic error}) { + logger.config(message, error); + } - /// Binding for critical logging. - late final criticalBinding = RuntimeBinding( - name: 'critical', - function: (dynamic message, {dynamic error}) => - logger.shout(message, error), - positionalParams: [ - const DynamicType(), - ], - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a critical message.', - ); + /// Logs messages at the shout level. + /// Louder than [severe]; use sparingly for attention-grabbing events. + @override + void shout(dynamic message, {dynamic error}) { + logger.shout(message, error); + } } diff --git a/lib/src/stdlib/log.g.dart b/lib/src/stdlib/log.g.dart new file mode 100644 index 0000000..56504d6 --- /dev/null +++ b/lib/src/stdlib/log.g.dart @@ -0,0 +1,350 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'log.dart'; + +// ************************************************************************** +// Generator: DscriptNamespace +// ************************************************************************** + +abstract class _$LogBindings extends LibraryBinding { + const _$LogBindings() + : super( + name: 'log', + description: + 'Provides basic logging utitlities.\n\n!> Log messages will not be visible when using [IsolateRuntime].'); + + @override + Set get bindings => { + infoBinding, + warningBinding, + severeBinding, + fineBinding, + finerBinding, + finestBinding, + configBinding, + shoutBinding + }; + + static final List> _infoPostMiddlewares = []; + static final List> _infoPreMiddlewares = []; + + bool _isinfoBinding(RuntimeBinding binding) { + return binding.name == 'info' && + binding.description == infoBinding.description && + binding.returnType == infoBinding.returnType && + binding.positionalParams == infoBinding.positionalParams && + binding.namedParams == infoBinding.namedParams && + binding.permissions == infoBinding.permissions; + } + + /// Logs general informational messages that highlight the progress of the application at a coarse level. + void info(dynamic message, {dynamic error}); + + /// Binding for [info]. + RuntimeBinding get infoBinding => RuntimeBinding( + name: 'info', + description: + 'Logs general informational messages that highlight the progress of the application at a coarse level.', + function: info, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _infoPreMiddlewares, + postMiddlewares: _infoPostMiddlewares, + ); + + static final List> _warningPostMiddlewares = []; + static final List> _warningPreMiddlewares = []; + + bool _iswarningBinding(RuntimeBinding binding) { + return binding.name == 'warning' && + binding.description == warningBinding.description && + binding.returnType == warningBinding.returnType && + binding.positionalParams == warningBinding.positionalParams && + binding.namedParams == warningBinding.namedParams && + binding.permissions == warningBinding.permissions; + } + + /// Logs potential problems that are not yet errors but might require attention or could lead to issues. + void warning(dynamic message, {dynamic error}); + + /// Binding for [warning]. + RuntimeBinding get warningBinding => RuntimeBinding( + name: 'warning', + description: + 'Logs potential problems that are not yet errors but might require attention or could lead to issues.', + function: warning, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _warningPreMiddlewares, + postMiddlewares: _warningPostMiddlewares, + ); + + static final List> _severePostMiddlewares = []; + static final List> _severePreMiddlewares = []; + + bool _issevereBinding(RuntimeBinding binding) { + return binding.name == 'severe' && + binding.description == severeBinding.description && + binding.returnType == severeBinding.returnType && + binding.positionalParams == severeBinding.positionalParams && + binding.namedParams == severeBinding.namedParams && + binding.permissions == severeBinding.permissions; + } + + /// Logs serious failures or errors that will likely prevent normal program execution. + void severe(dynamic message, {dynamic error}); + + /// Binding for [severe]. + RuntimeBinding get severeBinding => RuntimeBinding( + name: 'severe', + description: + 'Logs serious failures or errors that will likely prevent normal program execution.', + function: severe, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _severePreMiddlewares, + postMiddlewares: _severePostMiddlewares, + ); + + static final List> _finePostMiddlewares = []; + static final List> _finePreMiddlewares = []; + + bool _isfineBinding(RuntimeBinding binding) { + return binding.name == 'fine' && + binding.description == fineBinding.description && + binding.returnType == fineBinding.returnType && + binding.positionalParams == fineBinding.positionalParams && + binding.namedParams == fineBinding.namedParams && + binding.permissions == fineBinding.permissions; + } + + /// Logs tracing information for debugging purposes. + /// Less verbose than [finer] or [finest]. + void fine(dynamic message, {dynamic error}); + + /// Binding for [fine]. + RuntimeBinding get fineBinding => RuntimeBinding( + name: 'fine', + description: + 'Logs tracing information for debugging purposes.\nLess verbose than [finer] or [finest].', + function: fine, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _finePreMiddlewares, + postMiddlewares: _finePostMiddlewares, + ); + + static final List> _finerPostMiddlewares = []; + static final List> _finerPreMiddlewares = []; + + bool _isfinerBinding(RuntimeBinding binding) { + return binding.name == 'finer' && + binding.description == finerBinding.description && + binding.returnType == finerBinding.returnType && + binding.positionalParams == finerBinding.positionalParams && + binding.namedParams == finerBinding.namedParams && + binding.permissions == finerBinding.permissions; + } + + /// Logs fairly detailed tracing information. + /// Useful when debugging complex flows with more granularity than [fine]. + void finer(dynamic message, {dynamic error}); + + /// Binding for [finer]. + RuntimeBinding get finerBinding => RuntimeBinding( + name: 'finer', + description: + 'Logs fairly detailed tracing information.\nUseful when debugging complex flows with more granularity than [fine].', + function: finer, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _finerPreMiddlewares, + postMiddlewares: _finerPostMiddlewares, + ); + + static final List> _finestPostMiddlewares = []; + static final List> _finestPreMiddlewares = []; + + bool _isfinestBinding(RuntimeBinding binding) { + return binding.name == 'finest' && + binding.description == finestBinding.description && + binding.returnType == finestBinding.returnType && + binding.positionalParams == finestBinding.positionalParams && + binding.namedParams == finestBinding.namedParams && + binding.permissions == finestBinding.permissions; + } + + /// Logs highly detailed tracing information. + /// Intended for deep debugging, usually too verbose for normal use. + void finest(dynamic message, {dynamic error}); + + /// Binding for [finest]. + RuntimeBinding get finestBinding => RuntimeBinding( + name: 'finest', + description: + 'Logs highly detailed tracing information.\nIntended for deep debugging, usually too verbose for normal use.', + function: finest, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _finestPreMiddlewares, + postMiddlewares: _finestPostMiddlewares, + ); + + static final List> _configPostMiddlewares = []; + static final List> _configPreMiddlewares = []; + + bool _isconfigBinding(RuntimeBinding binding) { + return binding.name == 'config' && + binding.description == configBinding.description && + binding.returnType == configBinding.returnType && + binding.positionalParams == configBinding.positionalParams && + binding.namedParams == configBinding.namedParams && + binding.permissions == configBinding.permissions; + } + + /// Logs static configuration messages. + /// Typically used to record startup settings or environment details. + void config(dynamic message, {dynamic error}); + + /// Binding for [config]. + RuntimeBinding get configBinding => RuntimeBinding( + name: 'config', + description: + 'Logs static configuration messages.\nTypically used to record startup settings or environment details.', + function: config, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _configPreMiddlewares, + postMiddlewares: _configPostMiddlewares, + ); + + static final List> _shoutPostMiddlewares = []; + static final List> _shoutPreMiddlewares = []; + + bool _isshoutBinding(RuntimeBinding binding) { + return binding.name == 'shout' && + binding.description == shoutBinding.description && + binding.returnType == shoutBinding.returnType && + binding.positionalParams == shoutBinding.positionalParams && + binding.namedParams == shoutBinding.namedParams && + binding.permissions == shoutBinding.permissions; + } + + /// Logs messages at the shout level. + /// Louder than [severe]; use sparingly for attention-grabbing events. + void shout(dynamic message, {dynamic error}); + + /// Binding for [shout]. + RuntimeBinding get shoutBinding => RuntimeBinding( + name: 'shout', + description: + 'Logs messages at the shout level.\nLouder than [severe]; use sparingly for attention-grabbing events.', + function: shout, + returnType: $Type.from('void'), + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, + permissions: const [], + preMiddlewares: _shoutPreMiddlewares, + postMiddlewares: _shoutPostMiddlewares, + ); + + /// Registers global middlewares for a [RuntimeBinding]. + /// + /// These middlewares will always run when a binding is called, regardless of the context. + /// + /// For context aware middlewares you can call the [RuntimeBinding.addPreBindingMiddleware] and [RuntimeBinding.addPostBindingMiddleware] methods on the binding directly. + void registerGlobalMiddlewares(RuntimeBinding binding, + {List> preMiddlewares = const [], + List> postMiddlewares = const []}) { + if (_isinfoBinding(binding)) { + _infoPreMiddlewares + .addAll(preMiddlewares as List>); + _infoPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_iswarningBinding(binding)) { + _warningPreMiddlewares + .addAll(preMiddlewares as List>); + _warningPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_issevereBinding(binding)) { + _severePreMiddlewares + .addAll(preMiddlewares as List>); + _severePostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isfineBinding(binding)) { + _finePreMiddlewares + .addAll(preMiddlewares as List>); + _finePostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isfinerBinding(binding)) { + _finerPreMiddlewares + .addAll(preMiddlewares as List>); + _finerPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isfinestBinding(binding)) { + _finestPreMiddlewares + .addAll(preMiddlewares as List>); + _finestPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isconfigBinding(binding)) { + _configPreMiddlewares + .addAll(preMiddlewares as List>); + _configPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + if (_isshoutBinding(binding)) { + _shoutPreMiddlewares + .addAll(preMiddlewares as List>); + _shoutPostMiddlewares + .addAll(postMiddlewares as List>); + + return; + } + + // If no binding matched, throw an error. + + throw ArgumentError.value(binding, 'binding', + 'Binding does not match any known bindings in log: void, void, void, void, void, void, void, void'); + } +} diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart new file mode 100644 index 0000000..f4268e7 --- /dev/null +++ b/lib/src/stdlib/map.dart @@ -0,0 +1,257 @@ +part of 'stdlib.dart'; + +/// Bindings for the map standard library. +class MapBindings extends LibraryBinding { + /// Bindings for the map standard library. + const MapBindings() + : super( + name: 'map', + description: 'Library for working with maps.', + ); + + @override + Set get bindings => { + lengthBinding, + isEmptyBinding, + isNotEmptyBinding, + containsKeyBinding, + containsValueBinding, + keysBinding, + valuesBinding, + addAllBinding, + clearBinding, + removeBinding, + keyOfBinding, + keysOfBinding, + entriesBinding, + copyBinding, + }; + + /// Binding for [Map.length]. + static final lengthBinding = RuntimeBinding( + name: 'length', + function: (Map map) => map.length, + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns the number of key-value pairs in the map.', + returnType: PrimitiveType.INT, + ); + + /// Binding for [Map.isEmpty]. + static final isEmptyBinding = RuntimeBinding( + name: 'isEmpty', + function: (Map map) => map.isEmpty, + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns true if the map is empty.', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [Map.isNotEmpty]. + static final isNotEmptyBinding = RuntimeBinding( + name: 'isNotEmpty', + function: (Map map) => map.isNotEmpty, + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns true if the map is not empty.', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [Map.containsKey]. + static final containsKeyBinding = RuntimeBinding( + name: 'containsKey', + function: (Map map, dynamic key) => map.containsKey(key), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'key': const DynamicType(), + }, + description: 'Returns true if the map contains the specified [key].', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [Map.containsValue]. + static final containsValueBinding = RuntimeBinding( + name: 'containsValue', + function: (Map map, dynamic value) => + map.containsValue(value), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'value': const DynamicType(), + }, + description: 'Returns true if the map contains the specified [value].', + returnType: PrimitiveType.BOOL, + ); + + /// Binding for [Map.keys]. + static final keysBinding = RuntimeBinding>( + name: 'keys', + function: (Map map) => map.keys.toList(), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns a list of all keys in the map.', + returnType: ListType(elementType: const DynamicType()), + ); + + /// Binding for [Map.values]. + static final valuesBinding = RuntimeBinding>( + name: 'values', + function: (Map map) => map.values.toList(), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns a list of all values in the map.', + returnType: ListType(elementType: const DynamicType()), + ); + + /// Binding for [Map.addAll]. + static final addAllBinding = RuntimeBinding( + name: 'addAll', + function: (Map map, Map other) => + map.addAll(other), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'other': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Adds all key-value pairs from [other] to the map.', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [Map.clear]. + static final clearBinding = RuntimeBinding( + name: 'clear', + function: (Map map) => map.clear(), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Removes all key-value pairs from the map.', + returnType: PrimitiveType.VOID, + ); + + /// Binding for [Map.remove]. + static final removeBinding = RuntimeBinding( + name: 'remove', + function: (Map map, dynamic key) => map.remove(key), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'key': const DynamicType(), + }, + description: + 'Removes the key-value pair for the specified [key] from the map.', + returnType: const DynamicType(), + ); + + /// Returns the first key associated with the specified [value]. + static final keyOfBinding = RuntimeBinding( + name: 'keyOf', + function: (Map map, dynamic value) { + for (var entry in map.entries) { + if (entry.value == value) { + return entry.key; + } + } + return null; + }, + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'value': const DynamicType(), + }, + description: 'Returns the first key associated with the specified [value].', + returnType: const DynamicType(), + ); + + /// Returns a list of keys associated with the specified [value]. + static final keysOfBinding = RuntimeBinding>( + name: 'keysOf', + function: (Map map, dynamic value) { + return map.entries + .where((entry) => entry.value == value) + .map((entry) => entry.key) + .toList(); + }, + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'value': const DynamicType(), + }, + description: + 'Returns a list of keys associated with the specified [value].', + returnType: ListType(elementType: const DynamicType()), + ); + + /// [Map.entries] binding. + static final entriesBinding = RuntimeBinding( + name: 'entries', + function: (Map map) => map.entries.map((entry) { + return Struct.mapEntry.fromDart(entry); + }).toList(), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns a list of key-value pairs in the map.', + returnType: ListType( + elementType: Struct.mapEntry, + ), + ); + + /// Returns a copy of the [map]. + static final copyBinding = RuntimeBinding>( + name: 'copy', + function: (Map map) => Map.from(map), + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, + description: 'Returns a copy of the [map].', + returnType: MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + ); +} diff --git a/lib/src/stdlib/math.dart b/lib/src/stdlib/math.dart index ea6ceb3..5cf088a 100644 --- a/lib/src/stdlib/math.dart +++ b/lib/src/stdlib/math.dart @@ -7,6 +7,7 @@ class MathBindings extends LibraryBinding { const MathBindings() : super( name: 'math', + description: 'Library for mathematical functions.', ); @override @@ -37,21 +38,22 @@ class MathBindings extends LibraryBinding { static final sqrtBinding = RuntimeBinding( name: 'sqrt', function: (num x) => sqrt(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns the positive square root of the value', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [pow] function. static final powBinding = RuntimeBinding( name: 'pow', function: (num x, num y) => pow(x, y), - positionalParams: [ - PrimitiveType.NUM, - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + 'y': PrimitiveType.NUM, + }, description: ''' Returns [x] to the power of [exponent]. @@ -79,106 +81,116 @@ This corresponds to the pow function defined in the IEEE Standard 754-2008. Notice that the result may overflow. If integers are represented as 64-bit numbers, an integer result may be truncated, and a double result may overflow to positive or negative [infinity]. ''', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [log] function. static final logBinding = RuntimeBinding( name: 'log', function: (num x) => log(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns the natural logarithm of the value.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [exp] function. static final expBinding = RuntimeBinding( name: 'exp', function: (num x) => exp(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns the natural exponent, [e], to the power [x].', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [sin] function. static final sinBinding = RuntimeBinding( name: 'sin', function: (num x) => sin(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns the sine of the value.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [cos] function. static final cosBinding = RuntimeBinding( - name: 'cos', - function: (num x) => cos(x), - positionalParams: [ - PrimitiveType.NUM, - ], - description: - 'Converts [x] to a [double] and returns the cosine of the value.'); + name: 'cos', + function: (num x) => cos(x), + positionalParams: { + 'x': PrimitiveType.NUM, + }, + description: + 'Converts [x] to a [double] and returns the cosine of the value.', + returnType: PrimitiveType.DOUBLE, + ); /// Binding for the [tan] function. static final tanBinding = RuntimeBinding( name: 'tan', function: (num x) => tan(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: ''' Converts [x] to a [double] and returns the tangent of the value. The tangent function is equivalent to sin(x)/cos(x) ''', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [asin] function. static final asinBinding = RuntimeBinding( name: 'asin', function: (num x) => asin(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns its arc sine in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [acos] function. static final acosBinding = RuntimeBinding( name: 'acos', function: (num x) => acos(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns its arc cosine in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [atan] function. static final atanBinding = RuntimeBinding( name: 'atan', function: (num x) => atan(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns its arc tangent in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [atan2] function. static final atan2Binding = RuntimeBinding( name: 'atan2', function: (num y, num x) => atan2(y, x), - positionalParams: [ - PrimitiveType.NUM, - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + 'y': PrimitiveType.NUM, + }, description: ''' A variant of [atan]. @@ -192,46 +204,51 @@ The result is negative when [a] is negative (including when [a] is the double -0 If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-axis, even if [b] is also equal to zero. The sign of [b] determines the direction of the vector along the x-axis. ''', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [abs] function. static final absBinding = RuntimeBinding( name: 'abs', function: (num x) => x.abs(), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Returns the absolute value of [x].', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [floor] function. static final floorBinding = RuntimeBinding( name: 'floor', function: (num x) => x.floor(), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Returns the largest integer less than or equal to [x].', + returnType: PrimitiveType.INT, ); /// Binding for the [ceil] function. static final ceilBinding = RuntimeBinding( name: 'ceil', function: (num x) => x.ceil(), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Returns the smallest integer greater than or equal to [x].', + returnType: PrimitiveType.INT, ); /// Binding for the [round] function. static final roundBinding = RuntimeBinding( name: 'round', function: (num x) => x.round(), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Rounds [x] number to the nearest integer.', + returnType: PrimitiveType.INT, ); /// Binding for the [clamp] function. @@ -243,51 +260,56 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax #min: PrimitiveType.NUM, #max: PrimitiveType.NUM, }, - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Clamps [x] number between a [min] and [max] value.', + returnType: PrimitiveType.NUM, ); /// Binding for the [min] function. static final minBinding = RuntimeBinding( name: 'min', function: (num a, num b) => min(a, b), - positionalParams: [ - PrimitiveType.NUM, - PrimitiveType.NUM, - ], + positionalParams: { + 'a': PrimitiveType.NUM, + 'b': PrimitiveType.NUM, + }, description: 'Returns the minimum of [a] and [b].', + returnType: PrimitiveType.NUM, ); /// Binding for the [max] function. static final maxBinding = RuntimeBinding( name: 'max', function: (num a, num b) => max(a, b), - positionalParams: [ - PrimitiveType.NUM, - PrimitiveType.NUM, - ], + positionalParams: { + 'a': PrimitiveType.NUM, + 'b': PrimitiveType.NUM, + }, description: 'Returns the maximum of [a] and [b].', + returnType: PrimitiveType.NUM, ); /// Converts degrees to radians. static final radBinding = RuntimeBinding( name: 'rad', function: (num degrees) => degrees * (pi / 180), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'degrees': PrimitiveType.NUM, + }, description: 'Converts [degrees] to radians.', + returnType: PrimitiveType.DOUBLE, ); /// Converts radians to degrees. static final degBinding = RuntimeBinding( name: 'deg', function: (num radians) => radians * (180 / pi), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'radians': PrimitiveType.NUM, + }, description: 'Converts [radians] to degrees.', + returnType: PrimitiveType.DOUBLE, ); } diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 1cf4e4c..7f34c17 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -1,14 +1,23 @@ // coverage:ignore-file +import 'dart:convert'; +import 'dart:io'; import 'dart:math'; +import 'package:dio/dio.dart'; import 'package:dscript_dart/dscript_dart.dart'; -import 'package:logging/logging.dart'; +import 'package:dscript_dart/src/stdlib/base64.dart'; +import 'package:dscript_dart/src/stdlib/log.dart'; import 'package:pub_semver/pub_semver.dart'; part 'math.dart'; part 'string.dart'; -part 'log.dart'; part 'fs.dart'; +part 'list.dart'; +part 'map.dart'; +part 'dynamic.dart'; +part 'http.dart'; +part 'json.dart'; +part 'utf8.dart'; /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math @@ -20,11 +29,15 @@ abstract class LibraryBinding { /// The name of the library. final String name; + /// The docstring to describe the library. + final String description; + /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math /// functions or string manipulations. const LibraryBinding({ required this.name, + required this.description, }); @override @@ -57,20 +70,48 @@ $name { int get hashCode => Object.hash(name, bindings); /// Standard library bindings. - static List stdLib([ScriptMetadata? metadata]) => [ + /// + /// This calls [standardLibrary] and sets [metadata] to a default value if not provided. + /// + /// See [standardLibrary] to override the default standard library bindings. + static List stdLib([ScriptMetadata? metadata]) { + return standardLibrary( + metadata ?? + ScriptMetadata( + author: '', + name: '', + version: Version(0, 0, 1), + description: '', + license: null, + repository: null, + website: null, + ), + ); + } + + /// The standard library bindings used by [stdLib]. + /// Defaults to [defaultStdLib]. + /// + /// Set this to a custom function to override the default standard library bindings, + /// which must be done **before** analyzing, compiling, or running any scripts, + /// in order to take effect. + static StdLib standardLibrary = defaultStdLib; + + /// The default standard library bindings returned by [standardLibrary] if not overridden. + static List defaultStdLib(ScriptMetadata metadata) => [ const MathBindings(), const StringBindings(), - LogBindings( - metadata ?? - ScriptMetadata( - author: '', - name: '', - version: Version(0, 0, 1), - description: '', - license: null, - repository: null, - website: null, - ), - ), + const FsBindings(), + const ListBindings(), + const MapBindings(), + const DynamicBindings(), + const HttpBindings(), + const JsonBindings(), + const Utf8Bindings(), + const Base64Bindings(), + LogBindings(metadata), ]; } + +/// A function that returns a list of standard library bindings. +typedef StdLib = List Function(ScriptMetadata metadata); diff --git a/lib/src/stdlib/string.dart b/lib/src/stdlib/string.dart index 8498b51..463c95d 100644 --- a/lib/src/stdlib/string.dart +++ b/lib/src/stdlib/string.dart @@ -7,6 +7,7 @@ class StringBindings extends LibraryBinding { const StringBindings() : super( name: 'string', + description: 'Library for working with strings.', ); @override @@ -33,51 +34,55 @@ class StringBindings extends LibraryBinding { static final fromCharCodeBinding = RuntimeBinding( name: 'fromCharCode', function: (int code) => String.fromCharCode(code), - positionalParams: [ - PrimitiveType.INT, - ], + positionalParams: { + 'code': PrimitiveType.INT, + }, description: 'Creates a string from a single character code [code].', + returnType: PrimitiveType.STRING, ); /// Binding for [String.fromCharCodes]. static final fromCharCodesBinding = RuntimeBinding( name: 'from', function: (List codes) => String.fromCharCodes(codes), - positionalParams: [ - ListType(elementType: PrimitiveType.INT), - ], + positionalParams: { + 'codes': ListType(elementType: PrimitiveType.INT), + }, description: 'Creates a string from a list of character codes [codes].', + returnType: PrimitiveType.STRING, ); /// Binding for [Object.toString]. static final fromBinding = RuntimeBinding( name: 'from', function: (dynamic obj) => obj.toString(), - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'obj': const DynamicType(), + }, description: 'String representation of [obj]. If [obj] is a string, it is returned unchanged; otherwise, it is stringfied.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.length]. static final lengthBinding = RuntimeBinding( name: 'length', function: (String str) => str.length, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Returns the length of [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.substring]. static final substringBinding = RuntimeBinding( name: 'substring', function: (String str, int start, {int? end}) => str.substring(start, end), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.INT, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'start': PrimitiveType.INT, + }, namedParams: { #end: PrimitiveType.INT.asNullable(), }, @@ -86,119 +91,130 @@ class StringBindings extends LibraryBinding { Both [start] and [end] must be non-negative and no greater than the string's length; [end], if provided, must be greater than or equal to [start]. If [end] is omitted, the substring extends to the end of the string.''', + returnType: PrimitiveType.STRING, ); /// Binding for [String.toUpperCase]. static final toUpperCaseBinding = RuntimeBinding( name: 'upper', function: (String str) => str.toUpperCase(), - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Converts [str] to uppercase.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.toLowerCase]. static final toLowerCaseBinding = RuntimeBinding( name: 'lower', function: (String str) => str.toLowerCase(), - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Converts [str] to lowercase.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.trim]. static final trimBinding = RuntimeBinding( name: 'trim', function: (String str) => str.trim(), - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Removes leading and trailing whitespace from [str] and returns the resulting string.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.split]. static final splitBinding = RuntimeBinding>( name: 'split', function: (String str, String pattern) => str.split(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Splits [str] into a list of substrings using [pattern] as the delimiter.', + returnType: ListType(elementType: PrimitiveType.STRING), ); /// Binding for [String.replaceAll]. static final replaceAllBinding = RuntimeBinding( name: 'replaceAll', function: (String str, String from, String to) => str.replaceAll(from, to), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'from': PrimitiveType.STRING, + 'to': PrimitiveType.STRING, + }, description: 'Replaces all occurrences of [from] with [to] in [str].', + returnType: PrimitiveType.STRING, ); /// Binding for [String.contains]. static final containsBinding = RuntimeBinding( name: 'contains', function: (String str, String pattern) => str.contains(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Returns true if [str] contains [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.startsWith]. static final startsWithBinding = RuntimeBinding( name: 'startsWith', function: (String str, String pattern) => str.startsWith(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Returns true if [str] starts with [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.endsWith]. static final endsWithBinding = RuntimeBinding( name: 'endsWith', function: (String str, String pattern) => str.endsWith(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Returns true if [str] ends with [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.indexOf]. static final indexOfBinding = RuntimeBinding( name: 'indexOf', function: (String str, String pattern) => str.indexOf(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Returns the index of the first occurrence of [pattern] in [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.lastIndexOf]. static final lastIndexOfBinding = RuntimeBinding( name: 'lastIndexOf', function: (String str, String pattern) => str.lastIndexOf(pattern), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'pattern': PrimitiveType.STRING, + }, description: 'Returns the index of the last occurrence of [pattern] in [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.replaceFirst]. @@ -206,11 +222,12 @@ class StringBindings extends LibraryBinding { name: 'replaceFirst', function: (String str, String from, String to) => str.replaceFirst(from, to), - positionalParams: [ - PrimitiveType.STRING, - PrimitiveType.STRING, - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + 'from': PrimitiveType.STRING, + 'to': PrimitiveType.STRING, + }, description: 'Replaces the first occurrence of [from] with [to] in [str].', + returnType: PrimitiveType.STRING, ); } diff --git a/lib/src/stdlib/utf8.dart b/lib/src/stdlib/utf8.dart new file mode 100644 index 0000000..f5ac30f --- /dev/null +++ b/lib/src/stdlib/utf8.dart @@ -0,0 +1,43 @@ +part of 'stdlib.dart'; + +/// Provides UTF-8 encoding and decoding functions. +class Utf8Bindings extends LibraryBinding { + /// Provides UTF-8 encoding and decoding functions. + const Utf8Bindings() + : super( + name: 'utf8', + description: 'Provides UTF-8 encoding and decoding functions.', + ); + + @override + Set get bindings => { + decodeBinding, + encodeBinding, + }; + + /// Binding for [utf8Decode]. + static final decodeBinding = RuntimeBinding( + name: 'decode', + description: '''Decodes the given [bytes] to a UTF-8 string.''', + function: (List bytes) { + return utf8.decode(bytes); + }, + positionalParams: { + 'bytes': ListType(elementType: PrimitiveType.INT), + }, + returnType: PrimitiveType.STRING, + ); + + /// Binding for [utf8Encode]. + static final encodeBinding = RuntimeBinding>( + name: 'encode', + description: '''Encodes the given [str] to a list of UTF-8 bytes.''', + function: (String str) { + return utf8.encode(str); + }, + positionalParams: { + 'str': PrimitiveType.STRING, + }, + returnType: ListType(elementType: PrimitiveType.INT), + ); +} diff --git a/lib/src/types.dart b/lib/src/types.dart index a4ba2a5..df3e1b5 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -1,5 +1,6 @@ // coverage:ignore-file import 'package:collection/collection.dart'; +import 'package:dio/dio.dart' show Response, Headers, RequestOptions; import 'package:dscript_dart/dscript_dart.dart'; import 'package:equatable/equatable.dart'; @@ -15,6 +16,8 @@ sealed class Signature extends Equatable { /// Converts this signature to a JSON map. Map toJson() => { ...toMap(), + // Not used for type inference + // ignore: no_type_to_string 'type': runtimeType.toString(), 'description': description, }; @@ -196,7 +199,7 @@ sealed class $Type extends Signature { elementType: $Type.from(elementType.trim()), ).asNullable(nullable); } else { - return Struct(name: type, fields: {}).asNullable(nullable); + return Struct.shallow(type).asNullable(nullable); } } } @@ -205,6 +208,8 @@ sealed class $Type extends Signature { static const structKey = '__type__'; /// Extracts the type from a value. + /// + /// **NOTE:** If the value is a [Struct] this will return a shallow struct. Use [$Type.lookup] to get the full struct. factory $Type.fromValue(dynamic value) { if (value == null) { return PrimitiveType.NULL; @@ -226,6 +231,14 @@ sealed class $Type extends Signature { elementType: $Type.fromValue(value.first), ); } else if (value is Map) { + // If the map has a special key indicating it's a struct type, return the struct type. + if (value.containsKey(structKey)) { + final structName = value[structKey]; + if (structName is String) { + return Struct.shallow(structName); + } + } + if (value.isEmpty) { return MapType( keyType: PrimitiveType.NULL, @@ -246,7 +259,7 @@ sealed class $Type extends Signature { throw ArgumentError.value(value, 'value', 'Cannot determine type of value'); } - /// Looks up a placeholder struct by its name in the provided [types] and [Struct.defaults] list. + /// Looks up a shallow struct by its name in the provided [types] and [Struct.defaults] list. /// /// Returns the struct if found, otherwise returns null. /// @@ -450,7 +463,7 @@ The [double] type is contagious. Operations on [double]s return [double] results } } -/// Represents a Map type (e.g., Map). +/// Represents a Map type (e.g., `Map`). class MapType extends $Type { /// The type of the keys in the map. final $Type keyType; @@ -458,7 +471,7 @@ class MapType extends $Type { /// The type of the values in the map. final $Type valueType; - /// Represents a Map type (e.g., Map). + /// Represents a Map type (e.g., `Map`). /// This class is used to represent key-value pairs in the Dscript language. const MapType({ required this.keyType, @@ -525,12 +538,12 @@ class MapType extends $Type { } } -/// Represents a List type (e.g., List). +/// Represents a List type (e.g., `List`). class ListType extends $Type { /// The type of the elements in the list. final $Type elementType; - /// Represents a List type (e.g., List). + /// Represents a List type (e.g., `List`). const ListType({ required this.elementType, super.nullable = false, @@ -584,17 +597,81 @@ class ListType extends $Type { } /// Represents a custom object type. -class Struct extends $Type { +class Struct extends $Type { /// The fields of the object. final Map fields; + /// Serializes this DSL struct to a dart object of type [T] + final T Function(Map) _toDart; + + /// Deserializes a dart object of type [T] to this DSL struct. + final Map Function(T) _fromDart; + + /// Serializes this DSL struct to a dart object of type [T] + /// + /// Returns a Dart object of type [T] + T toDart(Map dsl) { + if (!dsl.containsKey($Type.structKey)) { + throw Exception( + 'The provided data does not contain the struct key: $Type.structKey'); + } + + if (dsl[$Type.structKey] != name) { + throw Exception( + 'The provided dsl struct is not of type $name; expected $name but got ${dsl[$Type.structKey]}'); + } + + return _toDart(dsl); + } + + /// Deserializes a dart object of type [T] to this DSL struct. + /// + /// Returns a [Map] with all the fields populated and the [$Type.structKey] set to [name]. + Map fromDart(T obj) { + final map = _fromDart(obj); + map[$Type.structKey] = name; // Add the struct key to the map + return map; + } + + static T _shallowToDart(Map json) { + throw UnimplementedError( + r'Cannot convert shallow struct to Dart object. Use `$Type.lookup` to get the resolved struct and call `toDart` on it.'); + } + + static Map _shallowFromDart(T obj) { + throw UnimplementedError( + r'Cannot convert Dart object to shallow struct. Use `$Type.lookup` to get the resolved struct and call `fromDart` on it.'); + } + /// Represents a custom object type. const Struct({ required super.name, this.fields = const {}, super.nullable = false, super.description, - }); + + /// Serializes this DSL struct to a dart object of type [T]. + /// + /// You can safely use assume that the map is well-formed and contains all the necessary fields. + required T Function(Map) toDart, + + /// Deserializes a dart object of type [T] to this DSL struct. + /// + /// The [$Type.structKey] field will be set to the name of the struct automatically. + required Map Function(T) fromDart, + }) : _fromDart = fromDart, + _toDart = toDart; + + /// Creates a shallow struct with only the name and no fields. + /// This is useful for defining hooks or implementations that reference a struct without needing to define its fields immediately. + const Struct.shallow(String name) + : this( + name: name, + fields: const {}, + nullable: false, + toDart: _shallowToDart, + fromDart: _shallowFromDart, + ); @override Map toMap() { @@ -614,6 +691,8 @@ class Struct extends $Type { name: name, fields: fields, nullable: nullable, + toDart: _toDart, + fromDart: _fromDart, ); } @@ -640,18 +719,168 @@ class Struct extends $Type { } /// Stdandard error struct used in Dscript. - static final error = Struct( + static final error = Struct( name: 'Error', fields: { 'message': PrimitiveType.STRING, 'stackTrace': PrimitiveType.STRING.asNullable(), }, - nullable: false, description: 'Represents an error with a message and stack trace.', + toDart: (json) => Exception( + json['message'] as String, + ), + fromDart: (obj) => { + 'message': obj.toString(), + 'stackTrace': StackTrace.current.toString(), + }, + ); + + /// Standard HTTP response struct used in Dscript. + static final httpResponse = Struct( + name: 'HttpResponse', + fields: { + 'statusCode': PrimitiveType.INT, + 'headers': MapType( + keyType: PrimitiveType.STRING, + valueType: ListType(elementType: PrimitiveType.STRING), + ), + 'data': PrimitiveType.STRING.asNullable(), + 'statusMessage': PrimitiveType.STRING.asNullable(), + 'isRedirect': PrimitiveType.BOOL, + }, + toDart: (json) => Response( + statusCode: json['statusCode'] as int, + headers: Headers.fromMap(json['headers'] as Map>), + data: json['data'] as String?, + requestOptions: RequestOptions(), + statusMessage: json['statusMessage'] as String?, + isRedirect: json['isRedirect'] as bool? ?? false, + ), + fromDart: (obj) => { + 'statusCode': obj.statusCode, + 'data': obj.data.toString(), + 'headers': obj.headers.map, + 'isRedirect': obj.isRedirect, + 'statusMessage': obj.statusMessage, + }, + description: + 'Represents an HTTP response with status code, headers, and body.', + ); + + /// Return value of [json::decode]. + static final Struct json = Struct( + name: 'JSON', + description: + "Result of [json::decode]. It's either a [Map] or a [List].", + fields: { + 'map': MapType( + keyType: PrimitiveType.STRING, valueType: const DynamicType()) + .asNullable(), + 'list': ListType(elementType: const DynamicType()).asNullable(), + 'isMap': PrimitiveType.BOOL, + 'isList': PrimitiveType.BOOL, + }, + toDart: (json) { + if (json['isMap'] == true) { + return json['map'] as Map; + } else if (json['isList'] == true) { + return json['list'] as List; + } + throw Exception('Invalid JSON structure'); + }, + fromDart: (json) => { + 'map': json is Map ? json : null, + 'list': json is List ? json : null, + 'isMap': json is Map, + 'isList': json is List, + }); + + /// Represents a key-value pair in a map. + static final mapEntry = Struct( + name: 'MapEntry', + description: 'Represents a key-value pair in a map.', + fields: { + 'key': const DynamicType(), + 'value': const DynamicType(), + }, + toDart: (json) => MapEntry( + json['key'], + json['value'], + ), + fromDart: (obj) => { + 'key': obj.key, + 'value': obj.value, + }, + ); + + /// Represents [DateTime]. + static final dateTime = Struct( + name: 'DateTime', + fields: { + 'year': PrimitiveType.INT, + 'month': PrimitiveType.INT, + 'day': PrimitiveType.INT, + 'hour': PrimitiveType.INT, + 'minute': PrimitiveType.INT, + 'second': PrimitiveType.INT, + 'millisecond': PrimitiveType.INT, + 'microsecond': PrimitiveType.INT, + }, + description: 'Represents a date and time.', + toDart: (json) => DateTime( + json['year'] as int, + json['month'] as int, + json['day'] as int, + json['hour'] as int, + json['minute'] as int, + json['second'] as int, + json['millisecond'] as int? ?? 0, + json['microsecond'] as int? ?? 0, + ), + fromDart: (obj) => { + 'year': obj.year, + 'month': obj.month, + 'day': obj.day, + 'hour': obj.hour, + 'minute': obj.minute, + 'second': obj.second, + 'millisecond': obj.millisecond, + 'microsecond': obj.microsecond, + }, + ); + + /// Represents [Duration]. + static final duration = Struct( + name: 'Duration', + fields: { + 'days': PrimitiveType.INT, + 'hours': PrimitiveType.INT, + 'minutes': PrimitiveType.INT, + 'seconds': PrimitiveType.INT, + 'milliseconds': PrimitiveType.INT, + 'microseconds': PrimitiveType.INT, + }, + description: 'Represents a duration of time.', + toDart: (json) => Duration( + days: json['days'] as int, + hours: json['hours'] as int, + minutes: json['minutes'] as int, + seconds: json['seconds'] as int, + milliseconds: json['milliseconds'] as int? ?? 0, + microseconds: json['microseconds'] as int? ?? 0, + ), + fromDart: (obj) => { + 'days': obj.inDays, + 'hours': obj.inHours.remainder(24), + 'minutes': obj.inMinutes.remainder(60), + 'seconds': obj.inSeconds.remainder(60), + 'milliseconds': obj.inMilliseconds.remainder(1000), + 'microseconds': obj.inMicroseconds.remainder(1000), + }, ); /// Default structs defined within the language. - static final defaults = [error]; + static final defaults = [error, httpResponse, json, duration, dateTime]; } /// Signature of a contract. diff --git a/lib/src/vm/vm.dart b/lib/src/vm/vm.dart index f2ad0dd..9d51fc1 100644 --- a/lib/src/vm/vm.dart +++ b/lib/src/vm/vm.dart @@ -1,5 +1,4 @@ import 'package:dscript_dart/dscript_dart.dart'; -import 'package:dscript_dart/src/stdlib/stdlib.dart'; part 'vm_impl.dart'; diff --git a/lib/src/vm/vm_impl.dart b/lib/src/vm/vm_impl.dart index eaa61f2..07ef279 100644 --- a/lib/src/vm/vm_impl.dart +++ b/lib/src/vm/vm_impl.dart @@ -333,6 +333,9 @@ class DefaultVM extends VM { final frame = buffer[ip++]; final index = buffer[ip++]; store(frame, index, stack.removeLast()); + + // pop this catch target to avoid infinite loops if the catch block throws again + if (catchTargets.isNotEmpty) catchTargets.removeLast(); break; case Instruction.endTry: if (catchTargets.isNotEmpty) catchTargets.removeLast(); diff --git a/pubspec.lock b/pubspec.lock index 88d6e19..c4d84fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "82.0.0" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: f4c21c94eb4623b183c1014a470196b3910701bea9b926e6c91270d756e6fc60 + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c url: "https://pub.dev" source: hosted - version: "7.4.1" + version: "7.6.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce + url: "https://pub.dev" + source: hosted + version: "0.13.4" antlr4: dependency: "direct main" description: @@ -49,6 +57,86 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f + url: "https://pub.dev" + source: hosted + version: "2.7.0" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7 + url: "https://pub.dev" + source: hosted + version: "9.3.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: ba95c961bafcd8686d1cf63be864eb59447e795e124d98d6a27d91fcd13602fb + url: "https://pub.dev" + source: hosted + version: "8.11.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" cli_config: dependency: transitive description: @@ -57,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" collection: dependency: "direct main" description: @@ -77,10 +181,10 @@ packages: dependency: transitive description: name: coverage - sha256: "9086475ef2da7102a0c0a4e37e1e30707e7fb7b6d28c209f559a9c5f8ce42016" + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" crypto: dependency: transitive description: @@ -89,6 +193,85 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "78085fbe842de7c5bef92de811ca81536968dbcbbcdac5c316711add2d15e796" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: cc5532d5733d4eccfccaaec6070a1926e9f21e613d93ad0927fad020b95c9e52 + url: "https://pub.dev" + source: hosted + version: "0.8.0" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: cc4684d22ca05bf0a4a51127e19a8aea576b42079ed2bc9e956f11aaebe35dd1 + url: "https://pub.dev" + source: hosted + version: "0.8.0" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.7.0" + custom_lints: + dependency: "direct dev" + description: + path: custom_lints + relative: true + source: path + version: "1.0.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dscript_annotations: + dependency: "direct main" + description: + name: dscript_annotations + sha256: b75cbdfa1d4fdac5a300e40beb77ff1f0b658ba2c4a0309dde4db5418a90781e + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dscript_gen: + dependency: "direct dev" + description: + name: dscript_gen + sha256: "3dbbc5581bffd1749e6a5ea782d4e65364edc561ed79b21a29fc02be7ea4f2c9" + url: "https://pub.dev" + source: hosted + version: "1.1.1" equatable: dependency: "direct main" description: @@ -105,6 +288,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -121,6 +320,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" http_multi_server: dependency: transitive description: @@ -149,18 +364,26 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" lints: dependency: "direct dev" description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: "direct main" description: @@ -233,14 +456,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" result_dart: dependency: "direct main" description: name: result_dart - sha256: eecf5b08b63572b348bfaa56a50f30191fe21d9edacef59d0e19a33905dd184d + sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" shelf: dependency: transitive description: @@ -273,6 +512,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" + url: "https://pub.dev" + source: hosted + version: "3.1.0" source_map_stack_trace: dependency: transitive description: @@ -297,14 +544,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - stack: - dependency: "direct main" + sprintf: + dependency: transitive description: - name: stack - sha256: f5a3032c965b74f394dc6aa138c82373a3403438c68cf5d8e6ef2bc2698949bf + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "7.0.0" stack_trace: dependency: transitive description: @@ -321,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -341,26 +596,34 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "1.0.2" typed_data: dependency: transitive description: @@ -369,22 +632,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: @@ -397,10 +668,10 @@ packages: dependency: transitive description: name: web_socket - sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: @@ -426,4 +697,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.4 <4.0.0" + dart: ">=3.8.1 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index fedb3f8..86c39f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,13 +9,22 @@ environment: dependencies: antlr4: ^4.13.2 collection: ^1.19.1 + dio: ^5.8.0+1 + dscript_annotations: ^1.0.0 equatable: ^2.0.7 logging: ^1.3.0 meta: ^1.17.0 pub_semver: ^2.2.0 result_dart: ^2.1.0 - stack: ^0.2.2 dev_dependencies: + build_runner: ^2.4.11 + custom_lint: ^0.8.0 + custom_lints: + path: ./custom_lints + dscript_gen: ^1.1.1 lints: ^5.0.0 test: ^1.24.0 +# dependency_overrides: +# dscript_gen: +# path: ../dscript_gen diff --git a/test/analyzer_test.dart b/test/analyzer_test.dart index b2b71ec..3a9f9fd 100644 --- a/test/analyzer_test.dart +++ b/test/analyzer_test.dart @@ -59,7 +59,8 @@ void main() { // `external::custom` permission, but the script omits it. final bindContract = contract('BindTest') .bind('testBind', (int x) => x * 2) - .param(PrimitiveType.INT) + .returns(PrimitiveType.DOUBLE) + .param('x', PrimitiveType.INT) .permission('custom') .end() .impl('useBind', returnType: PrimitiveType.DOUBLE) diff --git a/test/shared.dart b/test/shared.dart index 33bd83b..ffed61f 100644 --- a/test/shared.dart +++ b/test/shared.dart @@ -1,5 +1,27 @@ import 'package:dscript_dart/dscript_dart.dart'; +class User { + String name; + int id; + + User({required this.name, required this.id}); +} + +final userStruct = Struct( + name: 'User', + fields: { + 'name': PrimitiveType.STRING, + 'id': PrimitiveType.INT, + }, + toDart: (json) => User( + name: json['name'] as String, + id: json['id'] as int, + ), + fromDart: (user) => { + 'name': user.name, + 'id': user.id, + }); + final randomContract = ContractSignature( name: 'Random', implementations: [ @@ -30,12 +52,7 @@ final randomContract = ContractSignature( ), HookSignature(name: 'onLogout', namedParameters: []), ], - structs: const [ - Struct(name: 'User', fields: { - 'name': PrimitiveType.STRING, - 'id': PrimitiveType.INT, - }), - ], + structs: [userStruct], bindings: ExternalBindings(), );