From ebc4f1e7bf24e082b3366346ac7b407e71d02522 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Sun, 15 Jun 2025 13:21:45 +0200 Subject: [PATCH 01/23] feat(stdlib): expand standard library --- .vscode/settings.json | 3 +- lib/src/analyzer/visitors/flow.dart | 6 +- lib/src/compiler/compiler.dart | 1 + lib/src/compiler/naivie_compiler.dart | 14 +- lib/src/stdlib/dynamic.dart | 74 +++++++ lib/src/stdlib/fs.dart | 292 ++++++++++++++++++++++++++ lib/src/stdlib/list.dart | 189 +++++++++++++++++ lib/src/stdlib/map.dart | 128 +++++++++++ lib/src/stdlib/stdlib.dart | 8 + pubspec.lock | 8 - pubspec.yaml | 1 - 11 files changed, 710 insertions(+), 14 deletions(-) create mode 100644 lib/src/stdlib/dynamic.dart create mode 100644 lib/src/stdlib/list.dart create mode 100644 lib/src/stdlib/map.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 806e888..1cc6922 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,8 @@ "analyzer", "compiler", "runtime", - "vm" + "vm", + "stdlib" ], "conventionalCommits.autoCommit": false, "conventionalCommits.gitmoji": false, diff --git a/lib/src/analyzer/visitors/flow.dart b/lib/src/analyzer/visitors/flow.dart index 2e457e6..5efc06b 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, ); diff --git a/lib/src/compiler/compiler.dart b/lib/src/compiler/compiler.dart index d7e0aee..0c3c8af 100644 --- a/lib/src/compiler/compiler.dart +++ b/lib/src/compiler/compiler.dart @@ -5,6 +5,7 @@ import 'package:antlr4/antlr4.dart'; import 'package:dscript_dart/dscript_dart.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptParser.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptVisitor.dart'; +import 'package:dscript_dart/src/stdlib/stdlib.dart'; import 'package:equatable/equatable.dart'; part 'naivie_compiler.dart'; diff --git a/lib/src/compiler/naivie_compiler.dart b/lib/src/compiler/naivie_compiler.dart index 63b155f..c6b08c0 100644 --- a/lib/src/compiler/naivie_compiler.dart +++ b/lib/src/compiler/naivie_compiler.dart @@ -239,7 +239,19 @@ class NaiveCompiler extends Compiler { // Condition: index < iterable.length emit(Instruction.read, indexTemp.frame, indexTemp.index); emit(Instruction.read, iterableTemp.frame, iterableTemp.index); - emit(Instruction.readProperty, addConstant('length')); + + // push iter on the stack. + emit(Instruction.read, iterableTemp.frame, iterableTemp.index); + + // 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.lt); final idx = prepareJump(Instruction.jumpIfFalse); diff --git a/lib/src/stdlib/dynamic.dart b/lib/src/stdlib/dynamic.dart new file mode 100644 index 0000000..625272c --- /dev/null +++ b/lib/src/stdlib/dynamic.dart @@ -0,0 +1,74 @@ +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'); + + @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: [const DynamicType()], + description: 'Converts [value] to a 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: [const DynamicType()], + description: 'Converts [value] to an 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: [const DynamicType()], + description: 'Converts [value] to a 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: [const DynamicType()], + description: 'Converts [value] to a 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; + } + return 1; // For non-collection types, return 1 + }, + positionalParams: [const DynamicType()], + description: + 'Returns the length of [value] if it is a collection or string, otherwise returns 1.', + ); + + /// Returns the type of the dynamic value as a string. + static final typeBinding = RuntimeBinding( + name: 'type', + function: (dynamic value) => $Type.fromValue(value).toString(), + positionalParams: [const DynamicType()], + description: 'Returns the type of [value] as a string.', + ); +} diff --git a/lib/src/stdlib/fs.dart b/lib/src/stdlib/fs.dart index 69b946b..56c0e17 100644 --- a/lib/src/stdlib/fs.dart +++ b/lib/src/stdlib/fs.dart @@ -1 +1,293 @@ 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'); + + @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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: 'Reads the contents of a file at the given path as a 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.', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.writeFiles], + description: 'Deletes a file at the given [path].', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: 'Checks if a file exists at the given [path].', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: + 'Lists the contents of a directory at the given [path]. Returns a list of file paths.', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.writeFiles], + description: + 'Creates a directory at the given [path]. If the directory already exists, it does nothing.', + ); + + /// Checks if the path is a directory. + static final isDirectoryBinding = RuntimeBinding( + name: 'isDir', + function: (String path) async { + return await FileSystemEntity.isDirectory(path); + }, + positionalParams: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: 'Checks if the path is a directory.', + ); + + /// Checks if the path is a file. + static final isFileBinding = RuntimeBinding( + name: 'isFile', + function: (String path) async { + return await FileSystemEntity.isFile(path); + }, + positionalParams: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: 'Checks if the path is a file.', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + description: + 'Gets the absolute path of a file or directory at the given [path].', + ); + + /// Returns the current working directory. + static final currentDirectoryBinding = RuntimeBinding( + name: 'cwd', + function: () => Directory.current.path, + positionalParams: [], + description: 'Returns the current working directory.', + ); + + /// 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].', + ); + + /// 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].', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + permissions: [ScriptPermission.readFiles], + description: 'Gets the size of a file at the given [path] in bytes.', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + description: + 'Gets the file extension of the given [path]. Returns an empty string if no extension is found.', + ); + + /// 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: [ + PrimitiveType.STRING, + ], + description: 'Gets the base name of the given [path].', + ); + + /// Gets the directory name of a file or directory. + static final dirnameBinding = RuntimeBinding( + name: 'dirname', + function: (String path) { + return FileSystemEntity.parentOf(path); + }, + positionalParams: [ + 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. +""", + ); +} diff --git a/lib/src/stdlib/list.dart b/lib/src/stdlib/list.dart new file mode 100644 index 0000000..dda209e --- /dev/null +++ b/lib/src/stdlib/list.dart @@ -0,0 +1,189 @@ +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'); + + @override + Set get bindings => { + lengthBinding, + isEmptyBinding, + isNotEmptyBinding, + addBinding, + addAllBinding, + clearBinding, + removeBinding, + removeAtBinding, + removeLastBinding, + insertBinding, + insertAllBinding, + indexOfBinding, + lastIndexOfBinding, + containsBinding, + }; + + /// Binding for [List.length]. + static final lengthBinding = RuntimeBinding( + name: 'length', + function: (List list) => list.length, + positionalParams: [ + ListType(elementType: const DynamicType()), + ], + description: 'Returns the number of elements in the list.', + ); + + /// Binding for [List.isEmpty]. + static final isEmptyBinding = RuntimeBinding( + name: 'isEmpty', + function: (List list) => list.isEmpty, + positionalParams: [ + ListType(elementType: const DynamicType()), + ], + description: 'Returns true if the list is empty.', + ); + + /// Binding for [List.isNotEmpty]. + static final isNotEmptyBinding = RuntimeBinding( + name: 'isNotEmpty', + function: (List list) => list.isNotEmpty, + positionalParams: [ + ListType(elementType: const DynamicType()), + ], + description: 'Returns true if the list is not empty.', + ); + + /// Binding for [List.add]. + static final addBinding = RuntimeBinding( + name: 'add', + function: (List list, dynamic element) => list.add(element), + positionalParams: [ + ListType(elementType: const DynamicType()), + const DynamicType(), + ], + description: 'Adds [element] to the end of the list.', + ); + + /// Binding for [List.addAll]. + static final addAllBinding = RuntimeBinding( + name: 'addAll', + function: (List list, List elements) => + list.addAll(elements), + positionalParams: [ + ListType(elementType: const DynamicType()), + ListType(elementType: const DynamicType()), + ], + description: 'Adds all [elements] to the end of the list.', + ); + + /// Binding for [List.clear]. + static final clearBinding = RuntimeBinding( + name: 'clear', + function: (List list) => list.clear(), + positionalParams: [ + ListType(elementType: const DynamicType()), + ], + description: 'Removes all elements from the list.', + ); + + /// Binding for [List.remove]. + static final removeBinding = RuntimeBinding( + name: 'remove', + function: (List list, dynamic element) => list.remove(element), + positionalParams: [ + ListType(elementType: const DynamicType()), + const DynamicType(), + ], + description: 'Removes the first occurrence of [element] from the list.', + ); + + /// Binding for [List.removeAt]. + static final removeAtBinding = RuntimeBinding( + name: 'removeAt', + function: (List list, int index) => list.removeAt(index), + positionalParams: [ + ListType(elementType: const DynamicType()), + PrimitiveType.INT, + ], + description: 'Removes and returns the element at [index] from the list.', + ); + + /// Binding for [List.removeLast]. + static final removeLastBinding = RuntimeBinding( + name: 'removeLast', + function: (List list) => list.removeLast(), + positionalParams: [ + ListType(elementType: const DynamicType()), + ], + description: 'Removes and returns the last element from the list.', + ); + + /// Binding for [List.insert]. + static final insertBinding = RuntimeBinding( + name: 'insert', + function: (List list, int index, dynamic element) => + list.insert(index, element), + positionalParams: [ + ListType(elementType: const DynamicType()), + PrimitiveType.INT, + const DynamicType(), + ], + description: 'Inserts [element] at [index] in the list.', + ); + + /// Binding for [List.insertAll]. + static final insertAllBinding = RuntimeBinding( + name: 'insertAll', + function: (List list, int index, List elements) => + list.insertAll(index, elements), + positionalParams: [ + ListType(elementType: const DynamicType()), + PrimitiveType.INT, + ListType(elementType: const DynamicType()), + ], + description: 'Inserts all [elements] at [index] in the list.', + ); + + /// Binding for [List.indexOf]. + static final indexOfBinding = RuntimeBinding( + name: 'indexOf', + function: (List list, dynamic element, {int? start}) => + list.indexOf(element, start ?? 0), + positionalParams: [ + ListType(elementType: const DynamicType()), + const DynamicType(), + ], + namedParams: { + #start: PrimitiveType.INT.asNullable(), + }, + description: + 'Returns the index of the first occurrence of [element] in the list, starting from [start].', + ); + + /// Binding for [List.lastIndexOf]. + static final lastIndexOfBinding = RuntimeBinding( + name: 'lastIndexOf', + function: (List list, dynamic element, {int? start}) => + list.lastIndexOf(element, start), + positionalParams: [ + ListType(elementType: const DynamicType()), + const DynamicType(), + ], + namedParams: { + #start: PrimitiveType.INT.asNullable(), + }, + description: + 'Returns the index of the last occurrence of [element] in the list, starting from [start].', + ); + + /// Binding for [List.contains]. + static final containsBinding = RuntimeBinding( + name: 'contains', + function: (List list, dynamic element) => list.contains(element), + positionalParams: [ + ListType(elementType: const DynamicType()), + const DynamicType(), + ], + description: 'Returns true if the list contains [element].', + ); +} diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart new file mode 100644 index 0000000..b9cd17f --- /dev/null +++ b/lib/src/stdlib/map.dart @@ -0,0 +1,128 @@ +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'); + + @override + Set get bindings => { + lengthBinding, + isEmptyBinding, + isNotEmptyBinding, + containsKeyBinding, + containsValueBinding, + keysBinding, + valuesBinding, + addAllBinding, + clearBinding, + removeBinding, + }; + + /// Binding for [Map.length]. + static final lengthBinding = RuntimeBinding( + name: 'length', + function: (Map map) => map.length, + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Returns the number of key-value pairs in the map.', + ); + + /// Binding for [Map.isEmpty]. + static final isEmptyBinding = RuntimeBinding( + name: 'isEmpty', + function: (Map map) => map.isEmpty, + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Returns true if the map is empty.', + ); + + /// Binding for [Map.isNotEmpty]. + static final isNotEmptyBinding = RuntimeBinding( + name: 'isNotEmpty', + function: (Map map) => map.isNotEmpty, + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Returns true if the map is not empty.', + ); + + /// Binding for [Map.containsKey]. + static final containsKeyBinding = RuntimeBinding( + name: 'containsKey', + function: (Map map, dynamic key) => map.containsKey(key), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + const DynamicType(), + ], + description: 'Returns true if the map contains the specified [key].', + ); + + /// Binding for [Map.containsValue]. + static final containsValueBinding = RuntimeBinding( + name: 'containsValue', + function: (Map map, dynamic value) => + map.containsValue(value), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + const DynamicType(), + ], + description: 'Returns true if the map contains the specified [value].', + ); + + /// Binding for [Map.keys]. + static final keysBinding = RuntimeBinding>( + name: 'keys', + function: (Map map) => map.keys.toList(), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Returns a list of all keys in the map.', + ); + + /// Binding for [Map.values]. + static final valuesBinding = RuntimeBinding>( + name: 'values', + function: (Map map) => map.values.toList(), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Returns a list of all values in the map.', + ); + + /// Binding for [Map.addAll]. + static final addAllBinding = RuntimeBinding( + name: 'addAll', + function: (Map map, Map other) => + map.addAll(other), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Adds all key-value pairs from [other] to the map.', + ); + + /// Binding for [Map.clear]. + static final clearBinding = RuntimeBinding( + name: 'clear', + function: (Map map) => map.clear(), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + ], + description: 'Removes all key-value pairs from the map.', + ); + + /// Binding for [Map.remove]. + static final removeBinding = RuntimeBinding( + name: 'remove', + function: (Map map, dynamic key) => map.remove(key), + positionalParams: [ + MapType(keyType: const DynamicType(), valueType: const DynamicType()), + const DynamicType(), + ], + description: + 'Removes the key-value pair for the specified [key] from the map.', + ); +} diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 1cf4e4c..025bf49 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -1,4 +1,5 @@ // coverage:ignore-file +import 'dart:io'; import 'dart:math'; import 'package:dscript_dart/dscript_dart.dart'; @@ -9,6 +10,9 @@ part 'math.dart'; part 'string.dart'; part 'log.dart'; part 'fs.dart'; +part 'list.dart'; +part 'map.dart'; +part 'dynamic.dart'; /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math @@ -60,6 +64,10 @@ $name { static List stdLib([ScriptMetadata? metadata]) => [ const MathBindings(), const StringBindings(), + const FsBindings(), + const ListBindings(), + const MapBindings(), + const DynamicBindings(), LogBindings( metadata ?? ScriptMetadata( diff --git a/pubspec.lock b/pubspec.lock index 88d6e19..bd8db85 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -297,14 +297,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - stack: - dependency: "direct main" - description: - name: stack - sha256: f5a3032c965b74f394dc6aa138c82373a3403438c68cf5d8e6ef2bc2698949bf - url: "https://pub.dev" - source: hosted - version: "0.2.2" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fedb3f8..523d286 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: meta: ^1.17.0 pub_semver: ^2.2.0 result_dart: ^2.1.0 - stack: ^0.2.2 dev_dependencies: lints: ^5.0.0 From ce70c9ec167bdd52a66a7168ce3d616d61df9894 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Sun, 15 Jun 2025 17:14:59 +0200 Subject: [PATCH 02/23] feat(stdlib): support binding middleware and custom stdlibs --- bin/dscript.dart | 4 ++ bin/test.dscript | 23 ++---- lib/src/bindings.dart | 141 ++++++++++++++++++++++++++++++------- lib/src/stdlib/stdlib.dart | 46 ++++++++---- 4 files changed, 156 insertions(+), 58 deletions(-) diff --git a/bin/dscript.dart b/bin/dscript.dart index 6f198ce..be4b114 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -88,4 +88,8 @@ void main(List arguments) async { 'randomNumber', args: {'foo': 42}, )); + + print(await runtime.run( + 'randomString', + )); } diff --git a/bin/test.dscript b/bin/test.dscript index 246d8d4..85f9958 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -39,25 +39,11 @@ contract Random { impl randomString() -> string { - while(true) { - log::info("Generating random string..."); - break; - } - - - - // - // try { - // log::info("test"); - // return 'test'; - // } catch (e) { - // log::error("Error", error: e); - // return e.message; - // } - - return "RandomString"; + return fs::read("bin/test.dscript"); } + + impl test() -> void { final string? test = "Test"; @@ -72,8 +58,7 @@ contract Random { var p = 1.3; p = 2.0; - return; - } + return;} hook onLogout() { // Hook implementation diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index 45e8d5d..e73355c 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -33,8 +33,18 @@ class RuntimeBinding { /// The return type of the function as a dsl type. $Type get returnType => $Type.from(T.toString()); + /// 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({ + RuntimeBinding({ required this.name, required this.function, this.namedParams = const {}, @@ -43,14 +53,78 @@ class RuntimeBinding { required this.description, }); + /// 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 { positionalArgs = List.from(positionalArgs); namedArgs = Map.from(namedArgs); - // Check if all named parameters are provided - for (final entry in namedParams.entries) { + // 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 $T, got ${result.runtimeType}', + ); + } + + // 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 binding.namedParams.entries) { final param = entry.key; final expectedType = entry.value; @@ -78,45 +152,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[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[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[i], + positionalArgs[i], ); } } @@ -182,3 +242,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/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 025bf49..92ef4ea 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -61,24 +61,44 @@ $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(), const FsBindings(), const ListBindings(), const MapBindings(), const DynamicBindings(), - LogBindings( - metadata ?? - ScriptMetadata( - author: '', - name: '', - version: Version(0, 0, 1), - description: '', - license: null, - repository: null, - website: null, - ), - ), + LogBindings(metadata), ]; } + +/// A function that returns a list of standard library bindings. +typedef StdLib = List Function(ScriptMetadata metadata); From 8cea24db3df859eba282fa39160cf892a92ccf16 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 16 Jun 2025 17:37:13 +0200 Subject: [PATCH 03/23] docs(stdlib): add script for generating docs --- bin/doc.dart | 130 +++ bin/dscript.dart | 2 +- bin/test.dscript | 11 +- docs/language/standard-library.md | 1023 +++++++++++++++++++- lib/dscript_dart.dart | 1 + lib/src/analyzer/visitors/expressions.dart | 2 +- lib/src/analyzer/visitors/visitors.dart | 1 - lib/src/bindings.dart | 14 +- lib/src/compiler/compiler.dart | 1 - lib/src/contract_builder.dart | 8 +- lib/src/runtime/runtime.dart | 1 - lib/src/stdlib/dynamic.dart | 30 +- lib/src/stdlib/fs.dart | 81 +- lib/src/stdlib/list.dart | 114 +-- lib/src/stdlib/log.dart | 62 +- lib/src/stdlib/map.dart | 107 +- lib/src/stdlib/math.dart | 129 +-- lib/src/stdlib/stdlib.dart | 4 + lib/src/stdlib/string.dart | 119 +-- lib/src/vm/vm.dart | 1 - test/analyzer_test.dart | 2 +- 21 files changed, 1529 insertions(+), 314 deletions(-) create mode 100644 bin/doc.dart diff --git a/bin/doc.dart b/bin/doc.dart new file mode 100644 index 0000000..c092709 --- /dev/null +++ b/bin/doc.dart @@ -0,0 +1,130 @@ +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.', + ); + + for (final glob in TypeScope.globals.entries) { + sb.writeln('- `${glob.key}`: ${glob.value.$2} *(${glob.value.$1})*'); + } + + 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); + 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}'); + + // 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!.trim().replaceAllMapped( + RegExp(r'\[([^\]]+)\]'), + (m) => '${m.group(1)}', + ); + + 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(); +} diff --git a/bin/dscript.dart b/bin/dscript.dart index be4b114..af5bd4f 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -51,7 +51,7 @@ void main(List arguments) async { ) .end() .bind('double', (int x) => x * 2) - .param(PrimitiveType.INT) + .param('x', PrimitiveType.INT) .describe( 'A simple function that doubles an integer.', ) diff --git a/bin/test.dscript b/bin/test.dscript index 85f9958..0b2c912 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -15,6 +15,7 @@ permissions external::math; contract Random { const bar = 5; + const srcFile = "bin/test.dscript"; func test(string name) { @@ -39,7 +40,15 @@ contract Random { impl randomString() -> string { - return fs::read("bin/test.dscript"); + 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"; + } } diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index ac9a339..307854e 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -1,16 +1,1021 @@ # 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) +- [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. +- `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)* + + +--- + +## math + +Library for mathematical functions. + +### sqrt + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the positive square root of the value + + +### pow + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the natural logarithm of the value. + + +### exp + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the natural exponent, e, to the power x. + + +### sin + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the sine of the value. + + +### cos + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns the cosine of the value. + + +### tan + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc sine in radians. + + +### acos + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc cosine in radians. + + +### atan + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Converts x to a double and returns its arc tangent in radians. + + +### atan2 + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the absolute value of x. + + +### floor + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the largest integer less than or equal to x. + + +### ceil + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Returns the smallest integer greater than or equal to x. + + +### round + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | + +Rounds x number to the nearest integer. + + +### clamp + +| Name | Type | Kind | +| --- | --- | --- | +| x | `double` | Positional (1) | +| min | `double` | Named | +| max | `double` | Named | + +Clamps x number between a min and max value. + + +### min + +| Name | Type | Kind | +| --- | --- | --- | +| a | `double` | Positional (1) | +| b | `double` | Positional (2) | + +Returns the minimum of a and b. + + +### max + +| Name | Type | Kind | +| --- | --- | --- | +| a | `double` | Positional (1) | +| b | `double` | Positional (2) | + +Returns the maximum of a and b. + + +### rad + +| Name | Type | Kind | +| --- | --- | --- | +| degrees | `double` | Positional (1) | + +Converts degrees to radians. + + +### deg + +| Name | Type | Kind | +| --- | --- | --- | +| radians | `double` | Positional (1) | + +Converts radians to degrees. + + + + +--- + +## string + +Library for working with strings. + +### length + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Returns the length of str. + + +### substring + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Converts str to uppercase. + + +### lower + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Converts str to lowercase. + + +### trim + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | + +Removes leading and trailing whitespace from str and returns the resulting string. + + +### split + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Splits str into a list of substrings using pattern as the delimiter. + + +### replaceAll + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str contains pattern. False otherwise. + + +### startsWith + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str starts with pattern. False otherwise. + + +### endsWith + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns true if str ends with pattern. False otherwise. + + +### indexOf + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns the index of the first occurrence of pattern in str. + + +### lastIndexOf + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +| pattern | `string` | Positional (2) | + +Returns the index of the last occurrence of pattern in str. + + +### replaceFirst + +| 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 + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| code | `int` | Positional (1) | + +Creates a string from a single character code code. + + +### from + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Reads the contents of a file at the given path as a string. + +#### Permissions + +`fs::read` + + +### write + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Deletes a file at the given path. + +#### Permissions + +`fs::write` + + +### exists + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if a file exists at the given path. + +#### Permissions + +`fs::read` + + +### ls + +| 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 + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if the path is a directory. + +#### Permissions + +`fs::read` + + +### isFile + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Checks if the path is a file. + +#### Permissions + +`fs::read` + + +### absolute + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the absolute path of a file or directory at the given path. + +#### Permissions + +`fs::read` + + +### cwd + +Returns the current working directory. + +#### Permissions + +`fs::read` + + +### move + +| Name | Type | Kind | +| --- | --- | --- | +| from | `string` | Named | +| to | `string` | Named | + +Moves a file or directory from from to to. + +#### Permissions + +`fs::write`, `fs::read` + + +### copy + +| Name | Type | Kind | +| --- | --- | --- | +| from | `string` | Named | +| to | `string` | Named | + +Copies a file or directory from from to to. + +#### Permissions + +`fs::write`, `fs::read` + + +### size + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the size of a file at the given path in bytes. + +#### Permissions + +`fs::read` + + +### extension + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| path | `string` | Positional (1) | + +Gets the base name of the given path. + + +### dirname + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns the number of elements in the list. + + +### isEmpty + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns true if the list is empty. + + +### isNotEmpty + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns true if the list is not empty. + + +### add + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Adds element to the end of the list. + + +### addAll + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| elements | `List` | Positional (2) | + +Adds all elements to the end of the list. + + +### clear + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes all elements from the list. + + +### remove + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes the first occurrence of element from the list. + + +### removeAt + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | + +Removes and returns the element at index from the list. + + +### removeLast + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Removes and returns the last element from the list. + + +### insert + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | +| element | `dynamic?` | Positional (3) | + +Inserts element at index in the list. + + +### insertAll + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| index | `int` | Positional (2) | +| elements | `List` | Positional (3) | + +Inserts all elements at index in the list. + + +### indexOf + +| 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 + +| 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 + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | +| element | `dynamic?` | Positional (2) | + +Returns true if the list contains element. + + + + +--- + +## map + +Library for working with maps. + +### length + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns the number of key-value pairs in the map. + + +### isEmpty + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns true if the map is empty. + + +### isNotEmpty + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns true if the map is not empty. + + +### containsKey + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| key | `dynamic?` | Positional (2) | + +Returns true if the map contains the specified key. + + +### containsValue + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| value | `dynamic?` | Positional (2) | + +Returns true if the map contains the specified value. + + +### keys + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a list of all keys in the map. + + +### values + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Returns a list of all values in the map. + + +### addAll + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| other | `Map` | Positional (2) | + +Adds all key-value pairs from other to the map. + + +### clear + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | + +Removes all key-value pairs from the map. + + +### remove + +| Name | Type | Kind | +| --- | --- | --- | +| map | `Map` | Positional (1) | +| key | `dynamic?` | Positional (2) | + +Removes the key-value pair for the specified key from the map. + + + + +--- + +## dynamic + +Various utilities for dynamic values. + +### toString + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a string. + + +### toInt + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to an int. + + +### toDouble + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a double. + + +### toBool + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Converts value to a bool. + + +### length + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Returns the length of value if it is a collection or string, otherwise returns 1. + + +### type + +| Name | Type | Kind | +| --- | --- | --- | +| value | `dynamic?` | Positional (1) | + +Returns the type of value as a string. + + + + +--- + +## log + +Logging utilities. + +### info + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs an info message. + + +### warning + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs a warning message. + + +### error + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs an error message. + + +### debug + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs a debug message. + + +### verbose + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs a verbose message. + + +### fatal + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs a fatal message. + + +### critical + +| Name | Type | Kind | +| --- | --- | --- | +| message | `dynamic?` | Positional (1) | +| error | `dynamic?` | Named | + +Logs a critical message. -## `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/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 e73355c..5b1f468 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. /// @@ -49,7 +49,7 @@ class RuntimeBinding { required this.function, this.namedParams = const {}, this.permissions = const [], - this.positionalParams = const [], + this.positionalParams = const {}, required this.description, }); @@ -167,15 +167,15 @@ class RuntimeBinding { final type = $Type.fromValue(positionalArgs[i]); - if (!type.canCast(binding.positionalParams[i])) { + if (!type.canCast(binding.positionalParams.values.elementAt(i))) { throw ArgumentError( - 'Invalid argument type for positional argument $i: expected ${binding.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( - binding.positionalParams[i], + binding.positionalParams.values.elementAt(i), positionalArgs[i], ); } @@ -219,12 +219,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); } diff --git a/lib/src/compiler/compiler.dart b/lib/src/compiler/compiler.dart index 0c3c8af..d7e0aee 100644 --- a/lib/src/compiler/compiler.dart +++ b/lib/src/compiler/compiler.dart @@ -5,7 +5,6 @@ import 'package:antlr4/antlr4.dart'; import 'package:dscript_dart/dscript_dart.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptParser.dart'; import 'package:dscript_dart/src/gen/antlr/dscriptVisitor.dart'; -import 'package:dscript_dart/src/stdlib/stdlib.dart'; import 'package:equatable/equatable.dart'; part 'naivie_compiler.dart'; diff --git a/lib/src/contract_builder.dart b/lib/src/contract_builder.dart index f83ec5f..3f71758 100644 --- a/lib/src/contract_builder.dart +++ b/lib/src/contract_builder.dart @@ -90,7 +90,7 @@ class BindingBuilder { final String _name; final List _permissions = []; final Function _function; - final List<$Type> _params = []; + final Map _params = {}; final Map _namedParams = {}; String _description = ''; @@ -104,8 +104,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; } @@ -141,7 +141,7 @@ class BindingBuilder { name: _name, function: _function, permissions: _permissions, - positionalParams: List.unmodifiable(_params), + positionalParams: Map.unmodifiable(_params), namedParams: Map.unmodifiable(_namedParams), description: _description.isNotEmpty ? _description : null, ); 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/dynamic.dart b/lib/src/stdlib/dynamic.dart index 625272c..312ff17 100644 --- a/lib/src/stdlib/dynamic.dart +++ b/lib/src/stdlib/dynamic.dart @@ -3,7 +3,11 @@ 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'); + const DynamicBindings() + : super( + name: 'dynamic', + description: 'Various utilities for dynamic values.', + ); @override Set get bindings => { @@ -19,7 +23,9 @@ class DynamicBindings extends LibraryBinding { static final toStringBinding = RuntimeBinding( name: 'toString', function: (dynamic value) => value.toString(), - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Converts [value] to a string.', ); @@ -28,7 +34,9 @@ class DynamicBindings extends LibraryBinding { name: 'toInt', function: (dynamic value) => value is int ? value : int.parse(value.toString()), - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Converts [value] to an int.', ); @@ -37,7 +45,9 @@ class DynamicBindings extends LibraryBinding { name: 'toDouble', function: (dynamic value) => value is double ? value : double.parse(value.toString()), - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Converts [value] to a double.', ); @@ -46,7 +56,9 @@ class DynamicBindings extends LibraryBinding { name: 'toBool', function: (dynamic value) => value is bool ? value : (value.toString().toLowerCase() == 'true'), - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Converts [value] to a bool.', ); @@ -59,7 +71,9 @@ class DynamicBindings extends LibraryBinding { } return 1; // For non-collection types, return 1 }, - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Returns the length of [value] if it is a collection or string, otherwise returns 1.', ); @@ -68,7 +82,9 @@ class DynamicBindings extends LibraryBinding { static final typeBinding = RuntimeBinding( name: 'type', function: (dynamic value) => $Type.fromValue(value).toString(), - positionalParams: [const DynamicType()], + positionalParams: { + 'value': const DynamicType(), + }, description: 'Returns the type of [value] as a string.', ); } diff --git a/lib/src/stdlib/fs.dart b/lib/src/stdlib/fs.dart index 56c0e17..77ba79a 100644 --- a/lib/src/stdlib/fs.dart +++ b/lib/src/stdlib/fs.dart @@ -3,7 +3,11 @@ 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'); + const FsBindings() + : super( + name: 'fs', + description: 'Library for interacting with the file system.', + ); @override Set get bindings => { @@ -35,9 +39,9 @@ class FsBindings extends LibraryBinding { } return await file.readAsString(); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Reads the contents of a file at the given path as a string.', ); @@ -73,9 +77,9 @@ class FsBindings extends LibraryBinding { await file.delete(); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.writeFiles], description: 'Deletes a file at the given [path].', ); @@ -87,9 +91,9 @@ class FsBindings extends LibraryBinding { final file = File(path); return await file.exists(); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Checks if a file exists at the given [path].', ); @@ -108,9 +112,9 @@ class FsBindings extends LibraryBinding { .map((file) => file.path) .toList(growable: false); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Lists the contents of a directory at the given [path]. Returns a list of file paths.', @@ -126,9 +130,9 @@ class FsBindings extends LibraryBinding { } await dir.create(recursive: true); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.writeFiles], description: 'Creates a directory at the given [path]. If the directory already exists, it does nothing.', @@ -140,9 +144,9 @@ class FsBindings extends LibraryBinding { function: (String path) async { return await FileSystemEntity.isDirectory(path); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Checks if the path is a directory.', ); @@ -153,9 +157,9 @@ class FsBindings extends LibraryBinding { function: (String path) async { return await FileSystemEntity.isFile(path); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Checks if the path is a file.', ); @@ -167,9 +171,10 @@ class FsBindings extends LibraryBinding { final entity = File(path); return entity.absolute.path; }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, + permissions: [ScriptPermission.readFiles], description: 'Gets the absolute path of a file or directory at the given [path].', ); @@ -178,7 +183,7 @@ class FsBindings extends LibraryBinding { static final currentDirectoryBinding = RuntimeBinding( name: 'cwd', function: () => Directory.current.path, - positionalParams: [], + permissions: [ScriptPermission.readFiles], description: 'Returns the current working directory.', ); @@ -228,9 +233,9 @@ class FsBindings extends LibraryBinding { } return await file.length(); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, permissions: [ScriptPermission.readFiles], description: 'Gets the size of a file at the given [path] in bytes.', ); @@ -247,9 +252,9 @@ class FsBindings extends LibraryBinding { return file.uri.pathSegments.last.split('.').last; }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, description: 'Gets the file extension of the given [path]. Returns an empty string if no extension is found.', ); @@ -267,9 +272,9 @@ class FsBindings extends LibraryBinding { return name; }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, description: 'Gets the base name of the given [path].', ); @@ -279,9 +284,9 @@ class FsBindings extends LibraryBinding { function: (String path) { return FileSystemEntity.parentOf(path); }, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'path': PrimitiveType.STRING, + }, description: r""" The parent path of a path. diff --git a/lib/src/stdlib/list.dart b/lib/src/stdlib/list.dart index dda209e..fccb1c3 100644 --- a/lib/src/stdlib/list.dart +++ b/lib/src/stdlib/list.dart @@ -3,7 +3,11 @@ 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'); + const ListBindings() + : super( + name: 'list', + description: 'Library for working with lists.', + ); @override Set get bindings => { @@ -27,9 +31,9 @@ class ListBindings extends LibraryBinding { static final lengthBinding = RuntimeBinding( name: 'length', function: (List list) => list.length, - positionalParams: [ - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Returns the number of elements in the list.', ); @@ -37,9 +41,9 @@ class ListBindings extends LibraryBinding { static final isEmptyBinding = RuntimeBinding( name: 'isEmpty', function: (List list) => list.isEmpty, - positionalParams: [ - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Returns true if the list is empty.', ); @@ -47,9 +51,9 @@ class ListBindings extends LibraryBinding { static final isNotEmptyBinding = RuntimeBinding( name: 'isNotEmpty', function: (List list) => list.isNotEmpty, - positionalParams: [ - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Returns true if the list is not empty.', ); @@ -57,11 +61,10 @@ class ListBindings extends LibraryBinding { static final addBinding = RuntimeBinding( name: 'add', function: (List list, dynamic element) => list.add(element), - positionalParams: [ - ListType(elementType: const DynamicType()), - const DynamicType(), - ], - description: 'Adds [element] to the end of the list.', + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, + description: 'Adds [element] to the end of the [list].', ); /// Binding for [List.addAll]. @@ -69,20 +72,20 @@ class ListBindings extends LibraryBinding { name: 'addAll', function: (List list, List elements) => list.addAll(elements), - positionalParams: [ - ListType(elementType: const DynamicType()), - ListType(elementType: const DynamicType()), - ], - description: 'Adds all [elements] to the end of the list.', + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'elements': ListType(elementType: const DynamicType()), + }, + description: 'Adds all [elements] to the end of the [list].', ); /// Binding for [List.clear]. static final clearBinding = RuntimeBinding( name: 'clear', function: (List list) => list.clear(), - positionalParams: [ - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Removes all elements from the list.', ); @@ -90,10 +93,9 @@ class ListBindings extends LibraryBinding { static final removeBinding = RuntimeBinding( name: 'remove', function: (List list, dynamic element) => list.remove(element), - positionalParams: [ - ListType(elementType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Removes the first occurrence of [element] from the list.', ); @@ -101,10 +103,10 @@ class ListBindings extends LibraryBinding { static final removeAtBinding = RuntimeBinding( name: 'removeAt', function: (List list, int index) => list.removeAt(index), - positionalParams: [ - ListType(elementType: const DynamicType()), - PrimitiveType.INT, - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + }, description: 'Removes and returns the element at [index] from the list.', ); @@ -112,9 +114,9 @@ class ListBindings extends LibraryBinding { static final removeLastBinding = RuntimeBinding( name: 'removeLast', function: (List list) => list.removeLast(), - positionalParams: [ - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + }, description: 'Removes and returns the last element from the list.', ); @@ -123,11 +125,11 @@ class ListBindings extends LibraryBinding { name: 'insert', function: (List list, int index, dynamic element) => list.insert(index, element), - positionalParams: [ - ListType(elementType: const DynamicType()), - PrimitiveType.INT, - const DynamicType(), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + 'element': const DynamicType(), + }, description: 'Inserts [element] at [index] in the list.', ); @@ -136,11 +138,11 @@ class ListBindings extends LibraryBinding { name: 'insertAll', function: (List list, int index, List elements) => list.insertAll(index, elements), - positionalParams: [ - ListType(elementType: const DynamicType()), - PrimitiveType.INT, - ListType(elementType: const DynamicType()), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'index': PrimitiveType.INT, + 'elements': ListType(elementType: const DynamicType()), + }, description: 'Inserts all [elements] at [index] in the list.', ); @@ -149,10 +151,10 @@ class ListBindings extends LibraryBinding { name: 'indexOf', function: (List list, dynamic element, {int? start}) => list.indexOf(element, start ?? 0), - positionalParams: [ - ListType(elementType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, namedParams: { #start: PrimitiveType.INT.asNullable(), }, @@ -165,10 +167,10 @@ class ListBindings extends LibraryBinding { name: 'lastIndexOf', function: (List list, dynamic element, {int? start}) => list.lastIndexOf(element, start), - positionalParams: [ - ListType(elementType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, namedParams: { #start: PrimitiveType.INT.asNullable(), }, @@ -180,10 +182,10 @@ class ListBindings extends LibraryBinding { static final containsBinding = RuntimeBinding( name: 'contains', function: (List list, dynamic element) => list.contains(element), - positionalParams: [ - ListType(elementType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'list': ListType(elementType: const DynamicType()), + 'element': const DynamicType(), + }, description: 'Returns true if the list contains [element].', ); } diff --git a/lib/src/stdlib/log.dart b/lib/src/stdlib/log.dart index 4a9e02f..a8f364b 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -7,7 +7,11 @@ class LogBindings extends LibraryBinding { late final Logger logger; /// Bindings for the logging standard library. - LogBindings(ScriptMetadata metadata) : super(name: 'log') { + LogBindings(ScriptMetadata metadata) + : super( + name: 'log', + description: 'Logging utilities.', + ) { logger = Logger( '[Dscript] ${metadata.author}.${metadata.name}@${metadata.version}', ); @@ -27,10 +31,10 @@ class LogBindings extends LibraryBinding { /// Binding for info logging. late final infoBinding = RuntimeBinding( name: 'info', - function: (dynamic message, {dynamic error}) => logger.info(message, error), - positionalParams: [ - const DynamicType(), - ], + function: (dynamic message, [dynamic error]) => logger.info(message, error), + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -40,11 +44,11 @@ class LogBindings extends LibraryBinding { /// Binding for warning logging. late final warningBinding = RuntimeBinding( name: 'warning', - function: (dynamic message, {dynamic error}) => + function: (dynamic message, [dynamic error]) => logger.warning(message, error), - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -54,12 +58,12 @@ class LogBindings extends LibraryBinding { /// Binding for error logging. late final errorBinding = RuntimeBinding( name: 'error', - function: (dynamic message, {dynamic error}) { + function: (dynamic message, [dynamic error]) { logger.severe(message, error); }, - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -69,10 +73,10 @@ class LogBindings extends LibraryBinding { /// Binding for debug logging. late final debugBinding = RuntimeBinding( name: 'debug', - function: (dynamic message, {dynamic error}) => logger.fine(message, error), - positionalParams: [ - const DynamicType(), - ], + function: (dynamic message, [dynamic error]) => logger.fine(message, error), + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -82,11 +86,11 @@ class LogBindings extends LibraryBinding { /// Binding for verbose logging. late final verboseBinding = RuntimeBinding( name: 'verbose', - function: (dynamic message, {dynamic error}) => + function: (dynamic message, [dynamic error]) => logger.finer(message, error), - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -96,11 +100,11 @@ class LogBindings extends LibraryBinding { /// Binding for fatal logging. late final fatalBinding = RuntimeBinding( name: 'fatal', - function: (dynamic message, {dynamic error}) => + function: (dynamic message, [dynamic error]) => logger.shout(message, error), - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, @@ -110,11 +114,11 @@ class LogBindings extends LibraryBinding { /// Binding for critical logging. late final criticalBinding = RuntimeBinding( name: 'critical', - function: (dynamic message, {dynamic error}) => + function: (dynamic message, [dynamic error]) => logger.shout(message, error), - positionalParams: [ - const DynamicType(), - ], + positionalParams: { + 'message': const DynamicType(), + }, namedParams: { #error: const DynamicType(), }, diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart index b9cd17f..ec1f992 100644 --- a/lib/src/stdlib/map.dart +++ b/lib/src/stdlib/map.dart @@ -3,7 +3,11 @@ 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'); + const MapBindings() + : super( + name: 'map', + description: 'Library for working with maps.', + ); @override Set get bindings => { @@ -23,9 +27,12 @@ class MapBindings extends LibraryBinding { static final lengthBinding = RuntimeBinding( name: 'length', function: (Map map) => map.length, - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Returns the number of key-value pairs in the map.', ); @@ -33,9 +40,12 @@ class MapBindings extends LibraryBinding { static final isEmptyBinding = RuntimeBinding( name: 'isEmpty', function: (Map map) => map.isEmpty, - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Returns true if the map is empty.', ); @@ -43,9 +53,12 @@ class MapBindings extends LibraryBinding { static final isNotEmptyBinding = RuntimeBinding( name: 'isNotEmpty', function: (Map map) => map.isNotEmpty, - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Returns true if the map is not empty.', ); @@ -53,10 +66,13 @@ class MapBindings extends LibraryBinding { static final containsKeyBinding = RuntimeBinding( name: 'containsKey', function: (Map map, dynamic key) => map.containsKey(key), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'key': const DynamicType(), + }, description: 'Returns true if the map contains the specified [key].', ); @@ -65,10 +81,13 @@ class MapBindings extends LibraryBinding { name: 'containsValue', function: (Map map, dynamic value) => map.containsValue(value), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - const DynamicType(), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + 'value': const DynamicType(), + }, description: 'Returns true if the map contains the specified [value].', ); @@ -76,9 +95,12 @@ class MapBindings extends LibraryBinding { static final keysBinding = RuntimeBinding>( name: 'keys', function: (Map map) => map.keys.toList(), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Returns a list of all keys in the map.', ); @@ -86,9 +108,12 @@ class MapBindings extends LibraryBinding { static final valuesBinding = RuntimeBinding>( name: 'values', function: (Map map) => map.values.toList(), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Returns a list of all values in the map.', ); @@ -97,10 +122,16 @@ class MapBindings extends LibraryBinding { name: 'addAll', function: (Map map, Map other) => map.addAll(other), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + 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.', ); @@ -108,9 +139,12 @@ class MapBindings extends LibraryBinding { static final clearBinding = RuntimeBinding( name: 'clear', function: (Map map) => map.clear(), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - ], + positionalParams: { + 'map': MapType( + keyType: const DynamicType(), + valueType: const DynamicType(), + ), + }, description: 'Removes all key-value pairs from the map.', ); @@ -118,10 +152,13 @@ class MapBindings extends LibraryBinding { static final removeBinding = RuntimeBinding( name: 'remove', function: (Map map, dynamic key) => map.remove(key), - positionalParams: [ - MapType(keyType: const DynamicType(), valueType: const DynamicType()), - const DynamicType(), - ], + 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.', ); diff --git a/lib/src/stdlib/math.dart b/lib/src/stdlib/math.dart index ea6ceb3..b1d2c76 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,9 +38,9 @@ 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', ); @@ -48,10 +49,10 @@ class MathBindings extends LibraryBinding { 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]. @@ -85,9 +86,9 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe 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.', ); @@ -96,9 +97,9 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe 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].', ); @@ -107,9 +108,9 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe 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.', ); @@ -118,9 +119,9 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe static final cosBinding = RuntimeBinding( name: 'cos', function: (num x) => cos(x), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'x': PrimitiveType.NUM, + }, description: 'Converts [x] to a [double] and returns the cosine of the value.'); @@ -128,9 +129,9 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe 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. @@ -142,9 +143,9 @@ The tangent function is equivalent to sin(x)/cos(x) 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.', ); @@ -153,9 +154,9 @@ The tangent function is equivalent to sin(x)/cos(x) 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.', ); @@ -164,9 +165,9 @@ The tangent function is equivalent to sin(x)/cos(x) 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.', ); @@ -175,10 +176,10 @@ The tangent function is equivalent to sin(x)/cos(x) 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]. @@ -198,9 +199,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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].', ); @@ -208,9 +209,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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].', ); @@ -218,9 +219,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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].', ); @@ -228,9 +229,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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.', ); @@ -243,9 +244,9 @@ 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.', ); @@ -253,10 +254,10 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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].', ); @@ -264,10 +265,10 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 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].', ); @@ -275,9 +276,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax static final radBinding = RuntimeBinding( name: 'rad', function: (num degrees) => degrees * (pi / 180), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'degrees': PrimitiveType.NUM, + }, description: 'Converts [degrees] to radians.', ); @@ -285,9 +286,9 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax static final degBinding = RuntimeBinding( name: 'deg', function: (num radians) => radians * (180 / pi), - positionalParams: [ - PrimitiveType.NUM, - ], + positionalParams: { + 'radians': PrimitiveType.NUM, + }, description: 'Converts [radians] to degrees.', ); } diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 92ef4ea..396c94b 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -24,11 +24,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 diff --git a/lib/src/stdlib/string.dart b/lib/src/stdlib/string.dart index 8498b51..3c6c3bc 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,9 +34,9 @@ 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].', ); @@ -43,9 +44,9 @@ class StringBindings extends LibraryBinding { 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].', ); @@ -53,9 +54,9 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -64,9 +65,9 @@ class StringBindings extends LibraryBinding { static final lengthBinding = RuntimeBinding( name: 'length', function: (String str) => str.length, - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Returns the length of [str].', ); @@ -74,10 +75,10 @@ class StringBindings extends LibraryBinding { 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(), }, @@ -92,9 +93,9 @@ class StringBindings extends LibraryBinding { static final toUpperCaseBinding = RuntimeBinding( name: 'upper', function: (String str) => str.toUpperCase(), - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Converts [str] to uppercase.', ); @@ -102,9 +103,9 @@ class StringBindings extends LibraryBinding { static final toLowerCaseBinding = RuntimeBinding( name: 'lower', function: (String str) => str.toLowerCase(), - positionalParams: [ - PrimitiveType.STRING, - ], + positionalParams: { + 'str': PrimitiveType.STRING, + }, description: 'Converts [str] to lowercase.', ); @@ -112,9 +113,9 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -123,10 +124,10 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -135,11 +136,11 @@ class StringBindings extends LibraryBinding { 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].', ); @@ -147,10 +148,10 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -158,10 +159,10 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -170,10 +171,10 @@ class StringBindings extends LibraryBinding { 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.', ); @@ -181,10 +182,10 @@ class StringBindings extends LibraryBinding { 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].', ); @@ -193,10 +194,10 @@ class StringBindings extends LibraryBinding { 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].', ); @@ -206,11 +207,11 @@ 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].', ); } 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/test/analyzer_test.dart b/test/analyzer_test.dart index b2b71ec..0659c5a 100644 --- a/test/analyzer_test.dart +++ b/test/analyzer_test.dart @@ -59,7 +59,7 @@ void main() { // `external::custom` permission, but the script omits it. final bindContract = contract('BindTest') .bind('testBind', (int x) => x * 2) - .param(PrimitiveType.INT) + .param('x', PrimitiveType.INT) .permission('custom') .end() .impl('useBind', returnType: PrimitiveType.DOUBLE) From f3e1d25eee1888295536d6b81e04793aff175e99 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 16 Jun 2025 20:58:38 +0200 Subject: [PATCH 04/23] fix(compiler): actually handle assignments --- lib/src/compiler/naivie_compiler.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/compiler/naivie_compiler.dart b/lib/src/compiler/naivie_compiler.dart index c6b08c0..cd194a7 100644 --- a/lib/src/compiler/naivie_compiler.dart +++ b/lib/src/compiler/naivie_compiler.dart @@ -632,6 +632,7 @@ class NaiveCompiler extends Compiler { ctx.throwStmt()?.accept(this); ctx.tryStmt()?.accept(this); ctx.throwStmt()?.accept(this); + ctx.assignment()?.accept(this); } @override From ec76354d64db28d13a41b80126d3270f18a50e12 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 16 Jun 2025 21:15:17 +0200 Subject: [PATCH 05/23] fix(stdlib): throw when trying to get length of an unsopported type --- docs/language/standard-library.md | 2 +- lib/src/stdlib/dynamic.dart | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index 307854e..22d031b 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -928,7 +928,7 @@ Various utilities for dynamic values. | --- | --- | --- | | value | `dynamic?` | Positional (1) | -Returns the length of value if it is a collection or string, otherwise returns 1. +Returns the length of value if it is a collection or string, otherwise throws. ### type diff --git a/lib/src/stdlib/dynamic.dart b/lib/src/stdlib/dynamic.dart index 312ff17..900c255 100644 --- a/lib/src/stdlib/dynamic.dart +++ b/lib/src/stdlib/dynamic.dart @@ -69,13 +69,18 @@ class DynamicBindings extends LibraryBinding { if (value is String || value is List || value is Map) { return value.length; } - return 1; // For non-collection types, return 1 + + 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 returns 1.', + 'Returns the length of [value] if it is a collection or string, otherwise throws.', ); /// Returns the type of the dynamic value as a string. From f6a74bc1313eb8ae40256c3767d7937b98f9eb79 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Tue, 17 Jun 2025 01:00:49 +0200 Subject: [PATCH 06/23] docs: update docs --- bin/doc.dart | 2 +- bin/test.dscript | 17 +-- docs/_sidebar.md | 1 + docs/advanced/compiler.md | 9 +- docs/advanced/middlewares.md | 52 +++++++++ docs/advanced/runtimes.md | 2 + docs/host/external-functions.md | 2 +- docs/language/standard-library.md | 188 ++++++++++++++++-------------- lib/src/stdlib/map.dart | 22 ++++ lib/src/types.dart | 5 + 10 files changed, 196 insertions(+), 104 deletions(-) create mode 100644 docs/advanced/middlewares.md diff --git a/bin/doc.dart b/bin/doc.dart index c092709..845dd8f 100644 --- a/bin/doc.dart +++ b/bin/doc.dart @@ -69,7 +69,7 @@ String _documentBinding(RuntimeBinding binding) { final sb = StringBuffer(); // Binding Header with anchor - sb.writeln('### ${binding.name}'); + sb.writeln('### ${binding.name} → `${binding.returnType}`'); // Combine positional and named params in one table final params = >[]; diff --git a/bin/test.dscript b/bin/test.dscript index 0b2c912..a772b3f 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -1,4 +1,4 @@ -schema ""; +schema ""; author "McQuenji"; version 0.0.1; @@ -23,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 { @@ -38,8 +38,7 @@ contract Random { } - impl randomString() -> string - { + impl randomString() -> string { try{ log::info("Reading file: " + fs::absolute(srcFile)); final contents = fs::read(srcFile); @@ -56,18 +55,20 @@ contract Random { 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;} + return; + } hook onLogout() { // Hook implementation diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 2f8153b..0691f9c 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -13,3 +13,4 @@ - Advanced - [Compiler](advanced/compiler.md) - [Choosing a Runtime](advanced/runtimes.md) + - [Middlewares](advanced/middlewares.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..7d56ebf --- /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 RuntimeBinding binding, + required List positionalArgs, + required Map 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 RuntimeBinding binding, + required List positionalArgs, + required Map namedArgs, + required dynamic 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..5c75596 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. 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 22d031b..5d35c9e 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -29,7 +29,7 @@ The following globals are available in the dscript runtime. They are not part of Library for mathematical functions. -### sqrt +### sqrt → `double` | Name | Type | Kind | | --- | --- | --- | @@ -38,7 +38,7 @@ Library for mathematical functions. Converts x to a double and returns the positive square root of the value -### pow +### pow → `double` | Name | Type | Kind | | --- | --- | --- | @@ -73,7 +73,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe
-### log +### log → `double` | Name | Type | Kind | | --- | --- | --- | @@ -82,7 +82,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns the natural logarithm of the value. -### exp +### exp → `double` | Name | Type | Kind | | --- | --- | --- | @@ -91,7 +91,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns the natural exponent, e, to the power x. -### sin +### sin → `double` | Name | Type | Kind | | --- | --- | --- | @@ -100,7 +100,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns the sine of the value. -### cos +### cos → `double` | Name | Type | Kind | | --- | --- | --- | @@ -109,7 +109,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns the cosine of the value. -### tan +### tan → `double` | Name | Type | Kind | | --- | --- | --- | @@ -121,7 +121,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe
-### asin +### asin → `double` | Name | Type | Kind | | --- | --- | --- | @@ -130,7 +130,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns its arc sine in radians. -### acos +### acos → `double` | Name | Type | Kind | | --- | --- | --- | @@ -139,7 +139,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns its arc cosine in radians. -### atan +### atan → `double` | Name | Type | Kind | | --- | --- | --- | @@ -148,7 +148,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe Converts x to a double and returns its arc tangent in radians. -### atan2 +### atan2 → `double` | Name | Type | Kind | | --- | --- | --- | @@ -169,7 +169,7 @@ If a is equal to zero, the vector (b,a) i
-### abs +### abs → `double` | Name | Type | Kind | | --- | --- | --- | @@ -178,7 +178,7 @@ If a is equal to zero, the vector (b,a) i Returns the absolute value of x. -### floor +### floor → `int` | Name | Type | Kind | | --- | --- | --- | @@ -187,7 +187,7 @@ If a is equal to zero, the vector (b,a) i Returns the largest integer less than or equal to x. -### ceil +### ceil → `int` | Name | Type | Kind | | --- | --- | --- | @@ -196,7 +196,7 @@ If a is equal to zero, the vector (b,a) i Returns the smallest integer greater than or equal to x. -### round +### round → `int` | Name | Type | Kind | | --- | --- | --- | @@ -205,7 +205,7 @@ If a is equal to zero, the vector (b,a) i Rounds x number to the nearest integer. -### clamp +### clamp → `double` | Name | Type | Kind | | --- | --- | --- | @@ -216,7 +216,7 @@ If a is equal to zero, the vector (b,a) i Clamps x number between a min and max value. -### min +### min → `double` | Name | Type | Kind | | --- | --- | --- | @@ -226,7 +226,7 @@ If a is equal to zero, the vector (b,a) i Returns the minimum of a and b. -### max +### max → `double` | Name | Type | Kind | | --- | --- | --- | @@ -236,7 +236,7 @@ If a is equal to zero, the vector (b,a) i Returns the maximum of a and b. -### rad +### rad → `double` | Name | Type | Kind | | --- | --- | --- | @@ -245,7 +245,7 @@ If a is equal to zero, the vector (b,a) i Converts degrees to radians. -### deg +### deg → `double` | Name | Type | Kind | | --- | --- | --- | @@ -262,7 +262,7 @@ If a is equal to zero, the vector (b,a) i Library for working with strings. -### length +### length → `int` | Name | Type | Kind | | --- | --- | --- | @@ -271,7 +271,7 @@ Library for working with strings. Returns the length of str. -### substring +### substring → `string` | Name | Type | Kind | | --- | --- | --- | @@ -286,7 +286,7 @@ Library for working with strings.
-### upper +### upper → `string` | Name | Type | Kind | | --- | --- | --- | @@ -295,7 +295,7 @@ Library for working with strings. Converts str to uppercase. -### lower +### lower → `string` | Name | Type | Kind | | --- | --- | --- | @@ -304,7 +304,7 @@ Library for working with strings. Converts str to lowercase. -### trim +### trim → `string` | Name | Type | Kind | | --- | --- | --- | @@ -313,7 +313,7 @@ Library for working with strings. Removes leading and trailing whitespace from str and returns the resulting string. -### split +### split → `List` | Name | Type | Kind | | --- | --- | --- | @@ -323,7 +323,7 @@ Library for working with strings. Splits str into a list of substrings using pattern as the delimiter. -### replaceAll +### replaceAll → `string` | Name | Type | Kind | | --- | --- | --- | @@ -334,7 +334,7 @@ Library for working with strings. Replaces all occurrences of from with to in str. -### contains +### contains → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -344,7 +344,7 @@ Library for working with strings. Returns true if str contains pattern. False otherwise. -### startsWith +### startsWith → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -354,7 +354,7 @@ Library for working with strings. Returns true if str starts with pattern. False otherwise. -### endsWith +### endsWith → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -364,7 +364,7 @@ Library for working with strings. Returns true if str ends with pattern. False otherwise. -### indexOf +### indexOf → `int` | Name | Type | Kind | | --- | --- | --- | @@ -374,7 +374,7 @@ Library for working with strings. Returns the index of the first occurrence of pattern in str. -### lastIndexOf +### lastIndexOf → `int` | Name | Type | Kind | | --- | --- | --- | @@ -384,7 +384,7 @@ Library for working with strings. Returns the index of the last occurrence of pattern in str. -### replaceFirst +### replaceFirst → `string` | Name | Type | Kind | | --- | --- | --- | @@ -395,7 +395,7 @@ Library for working with strings. Replaces the first occurrence of from with to in str. -### from +### from → `string` | Name | Type | Kind | | --- | --- | --- | @@ -404,7 +404,7 @@ Library for working with strings. String representation of obj. If obj is a string, it is returned unchanged; otherwise, it is stringfied. -### fromCharCode +### fromCharCode → `string` | Name | Type | Kind | | --- | --- | --- | @@ -413,7 +413,7 @@ Library for working with strings. Creates a string from a single character code code. -### from +### from → `string` | Name | Type | Kind | | --- | --- | --- | @@ -430,7 +430,7 @@ Library for working with strings. Library for interacting with the file system. -### read +### read → `string` | Name | Type | Kind | | --- | --- | --- | @@ -443,7 +443,7 @@ Library for interacting with the file system. `fs::read` -### write +### write → `void` | Name | Type | Kind | | --- | --- | --- | @@ -458,7 +458,7 @@ Library for interacting with the file system. `fs::write` -### delete +### delete → `void` | Name | Type | Kind | | --- | --- | --- | @@ -471,7 +471,7 @@ Library for interacting with the file system. `fs::write` -### exists +### exists → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -484,7 +484,7 @@ Library for interacting with the file system. `fs::read` -### ls +### ls → `List` | Name | Type | Kind | | --- | --- | --- | @@ -497,7 +497,7 @@ Library for interacting with the file system. `fs::read` -### mkdir +### mkdir → `void` | Name | Type | Kind | | --- | --- | --- | @@ -510,7 +510,7 @@ Library for interacting with the file system. `fs::write` -### isDir +### isDir → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -523,7 +523,7 @@ Library for interacting with the file system. `fs::read` -### isFile +### isFile → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -536,7 +536,7 @@ Library for interacting with the file system. `fs::read` -### absolute +### absolute → `string` | Name | Type | Kind | | --- | --- | --- | @@ -549,7 +549,7 @@ Library for interacting with the file system. `fs::read` -### cwd +### cwd → `string` Returns the current working directory. @@ -558,7 +558,7 @@ Library for interacting with the file system. `fs::read` -### move +### move → `void` | Name | Type | Kind | | --- | --- | --- | @@ -572,7 +572,7 @@ Library for interacting with the file system. `fs::write`, `fs::read` -### copy +### copy → `void` | Name | Type | Kind | | --- | --- | --- | @@ -586,7 +586,7 @@ Library for interacting with the file system. `fs::write`, `fs::read` -### size +### size → `int` | Name | Type | Kind | | --- | --- | --- | @@ -599,7 +599,7 @@ Library for interacting with the file system. `fs::read` -### extension +### extension → `string` | Name | Type | Kind | | --- | --- | --- | @@ -608,7 +608,7 @@ Library for interacting with the file system. Gets the file extension of the given path. Returns an empty string if no extension is found. -### basename +### basename → `string` | Name | Type | Kind | | --- | --- | --- | @@ -617,7 +617,7 @@ Library for interacting with the file system. Gets the base name of the given path. -### dirname +### dirname → `string` | Name | Type | Kind | | --- | --- | --- | @@ -639,7 +639,7 @@ Will not remove the root component of a Windows path, like "C:\" or "\\server_na Library for working with lists. -### length +### length → `int` | Name | Type | Kind | | --- | --- | --- | @@ -648,7 +648,7 @@ Library for working with lists. Returns the number of elements in the list. -### isEmpty +### isEmpty → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -657,7 +657,7 @@ Library for working with lists. Returns true if the list is empty. -### isNotEmpty +### isNotEmpty → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -666,7 +666,7 @@ Library for working with lists. Returns true if the list is not empty. -### add +### add → `void` | Name | Type | Kind | | --- | --- | --- | @@ -675,7 +675,7 @@ Library for working with lists. Adds element to the end of the list. -### addAll +### addAll → `void` | Name | Type | Kind | | --- | --- | --- | @@ -685,7 +685,7 @@ Library for working with lists. Adds all elements to the end of the list. -### clear +### clear → `void` | Name | Type | Kind | | --- | --- | --- | @@ -694,7 +694,7 @@ Library for working with lists. Removes all elements from the list. -### remove +### remove → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -703,7 +703,7 @@ Library for working with lists. Removes the first occurrence of element from the list. -### removeAt +### removeAt → `dynamic` | Name | Type | Kind | | --- | --- | --- | @@ -713,7 +713,7 @@ Library for working with lists. Removes and returns the element at index from the list. -### removeLast +### removeLast → `dynamic` | Name | Type | Kind | | --- | --- | --- | @@ -722,7 +722,7 @@ Library for working with lists. Removes and returns the last element from the list. -### insert +### insert → `void` | Name | Type | Kind | | --- | --- | --- | @@ -733,7 +733,7 @@ Library for working with lists. Inserts element at index in the list. -### insertAll +### insertAll → `void` | Name | Type | Kind | | --- | --- | --- | @@ -744,7 +744,7 @@ Library for working with lists. Inserts all elements at index in the list. -### indexOf +### indexOf → `int` | Name | Type | Kind | | --- | --- | --- | @@ -755,7 +755,7 @@ Library for working with lists. Returns the index of the first occurrence of element in the list, starting from start. -### lastIndexOf +### lastIndexOf → `int` | Name | Type | Kind | | --- | --- | --- | @@ -766,7 +766,7 @@ Library for working with lists. Returns the index of the last occurrence of element in the list, starting from start. -### contains +### contains → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -784,7 +784,7 @@ Library for working with lists. Library for working with maps. -### length +### length → `int` | Name | Type | Kind | | --- | --- | --- | @@ -793,7 +793,7 @@ Library for working with maps. Returns the number of key-value pairs in the map. -### isEmpty +### isEmpty → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -802,7 +802,7 @@ Library for working with maps. Returns true if the map is empty. -### isNotEmpty +### isNotEmpty → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -811,7 +811,7 @@ Library for working with maps. Returns true if the map is not empty. -### containsKey +### containsKey → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -821,7 +821,7 @@ Library for working with maps. Returns true if the map contains the specified key. -### containsValue +### containsValue → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -831,7 +831,7 @@ Library for working with maps. Returns true if the map contains the specified value. -### keys +### keys → `List` | Name | Type | Kind | | --- | --- | --- | @@ -840,7 +840,7 @@ Library for working with maps. Returns a list of all keys in the map. -### values +### values → `List` | Name | Type | Kind | | --- | --- | --- | @@ -849,7 +849,7 @@ Library for working with maps. Returns a list of all values in the map. -### addAll +### addAll → `void` | Name | Type | Kind | | --- | --- | --- | @@ -859,7 +859,7 @@ Library for working with maps. Adds all key-value pairs from other to the map. -### clear +### clear → `void` | Name | Type | Kind | | --- | --- | --- | @@ -868,7 +868,7 @@ Library for working with maps. Removes all key-value pairs from the map. -### remove +### remove → `dynamic` | Name | Type | Kind | | --- | --- | --- | @@ -878,6 +878,16 @@ Library for working with maps. 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 key associated with the specified value. + + --- @@ -886,7 +896,7 @@ Library for working with maps. Various utilities for dynamic values. -### toString +### toString → `string` | Name | Type | Kind | | --- | --- | --- | @@ -895,7 +905,7 @@ Various utilities for dynamic values. Converts value to a string. -### toInt +### toInt → `int` | Name | Type | Kind | | --- | --- | --- | @@ -904,7 +914,7 @@ Various utilities for dynamic values. Converts value to an int. -### toDouble +### toDouble → `double` | Name | Type | Kind | | --- | --- | --- | @@ -913,7 +923,7 @@ Various utilities for dynamic values. Converts value to a double. -### toBool +### toBool → `bool` | Name | Type | Kind | | --- | --- | --- | @@ -922,7 +932,7 @@ Various utilities for dynamic values. Converts value to a bool. -### length +### length → `int` | Name | Type | Kind | | --- | --- | --- | @@ -931,7 +941,7 @@ Various utilities for dynamic values. Returns the length of value if it is a collection or string, otherwise throws. -### type +### type → `string` | Name | Type | Kind | | --- | --- | --- | @@ -948,7 +958,7 @@ Various utilities for dynamic values. Logging utilities. -### info +### info → `void` | Name | Type | Kind | | --- | --- | --- | @@ -958,7 +968,7 @@ Logging utilities. Logs an info message. -### warning +### warning → `void` | Name | Type | Kind | | --- | --- | --- | @@ -968,7 +978,7 @@ Logging utilities. Logs a warning message. -### error +### error → `void` | Name | Type | Kind | | --- | --- | --- | @@ -978,7 +988,7 @@ Logging utilities. Logs an error message. -### debug +### debug → `void` | Name | Type | Kind | | --- | --- | --- | @@ -988,7 +998,7 @@ Logging utilities. Logs a debug message. -### verbose +### verbose → `void` | Name | Type | Kind | | --- | --- | --- | @@ -998,7 +1008,7 @@ Logging utilities. Logs a verbose message. -### fatal +### fatal → `void` | Name | Type | Kind | | --- | --- | --- | @@ -1008,7 +1018,7 @@ Logging utilities. Logs a fatal message. -### critical +### critical → `void` | Name | Type | Kind | | --- | --- | --- | diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart index ec1f992..841ea18 100644 --- a/lib/src/stdlib/map.dart +++ b/lib/src/stdlib/map.dart @@ -21,6 +21,7 @@ class MapBindings extends LibraryBinding { addAllBinding, clearBinding, removeBinding, + keyOfBinding, }; /// Binding for [Map.length]. @@ -162,4 +163,25 @@ class MapBindings extends LibraryBinding { description: 'Removes the key-value pair for the specified [key] from the map.', ); + + /// B + 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 key associated with the specified [value].', + ); } diff --git a/lib/src/types.dart b/lib/src/types.dart index a4ba2a5..8e6c630 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -596,6 +596,11 @@ class Struct extends $Type { super.description, }); + /// 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); + @override Map toMap() { return { From a22a95adc34382ecef3f41a7aa0fa22e4811e640 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Tue, 17 Jun 2025 01:10:51 +0200 Subject: [PATCH 07/23] fix(vm): resolve infinite loop when catch block throws The infinite loop was caused by the catch target not being popped after entering it. This caused all exceptions thrown within the catch block to just jump to the start of the block, thus infinitley calling itself instead of just erroring out --- bin/dscript.dart | 3 +++ lib/src/bindings.dart | 12 +++++++++--- lib/src/stdlib/log.dart | 14 +++++++------- lib/src/vm/vm_impl.dart | 3 +++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bin/dscript.dart b/bin/dscript.dart index af5bd4f..c73f5af 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -89,6 +89,9 @@ void main(List arguments) async { args: {'foo': 42}, )); + print(bytecode.implementations['randomString']?.toDebugString() ?? + 'No implementation found for randomString'); + print(await runtime.run( 'randomString', )); diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index 5b1f468..185c061 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -70,8 +70,10 @@ class RuntimeBinding { } /// 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); @@ -84,12 +86,16 @@ class RuntimeBinding { ); } + print('Calling binding: $name'); + var result = await Function.apply( function, positionalArgs, namedArgs, ); + print('Binding $name returned: $result'); + final resultType = $Type.fromValue(result); if (resultType.canCast(returnType)) { @@ -123,7 +129,7 @@ class RuntimeBinding { required List positionalArgs, required Map namedArgs, }) { -// Check if all named parameters are provided + // Check if all named parameters are provided for (final entry in binding.namedParams.entries) { final param = entry.key; final expectedType = entry.value; diff --git a/lib/src/stdlib/log.dart b/lib/src/stdlib/log.dart index a8f364b..23eb523 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -31,7 +31,7 @@ class LogBindings extends LibraryBinding { /// Binding for info logging. late final infoBinding = RuntimeBinding( name: 'info', - function: (dynamic message, [dynamic error]) => logger.info(message, error), + function: (dynamic message, {dynamic error}) => logger.info(message, error), positionalParams: { 'message': const DynamicType(), }, @@ -44,7 +44,7 @@ class LogBindings extends LibraryBinding { /// Binding for warning logging. late final warningBinding = RuntimeBinding( name: 'warning', - function: (dynamic message, [dynamic error]) => + function: (dynamic message, {dynamic error}) => logger.warning(message, error), positionalParams: { 'message': const DynamicType(), @@ -58,7 +58,7 @@ class LogBindings extends LibraryBinding { /// Binding for error logging. late final errorBinding = RuntimeBinding( name: 'error', - function: (dynamic message, [dynamic error]) { + function: (dynamic message, {dynamic error}) { logger.severe(message, error); }, positionalParams: { @@ -73,7 +73,7 @@ class LogBindings extends LibraryBinding { /// Binding for debug logging. late final debugBinding = RuntimeBinding( name: 'debug', - function: (dynamic message, [dynamic error]) => logger.fine(message, error), + function: (dynamic message, {dynamic error}) => logger.fine(message, error), positionalParams: { 'message': const DynamicType(), }, @@ -86,7 +86,7 @@ class LogBindings extends LibraryBinding { /// Binding for verbose logging. late final verboseBinding = RuntimeBinding( name: 'verbose', - function: (dynamic message, [dynamic error]) => + function: (dynamic message, {dynamic error}) => logger.finer(message, error), positionalParams: { 'message': const DynamicType(), @@ -100,7 +100,7 @@ class LogBindings extends LibraryBinding { /// Binding for fatal logging. late final fatalBinding = RuntimeBinding( name: 'fatal', - function: (dynamic message, [dynamic error]) => + function: (dynamic message, {dynamic error}) => logger.shout(message, error), positionalParams: { 'message': const DynamicType(), @@ -114,7 +114,7 @@ class LogBindings extends LibraryBinding { /// Binding for critical logging. late final criticalBinding = RuntimeBinding( name: 'critical', - function: (dynamic message, [dynamic error]) => + function: (dynamic message, {dynamic error}) => logger.shout(message, error), positionalParams: { 'message': const DynamicType(), 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(); From 3c6209b0f352c81d02e3ec3ed7e74254ca4d1fd3 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Tue, 17 Jun 2025 01:12:07 +0200 Subject: [PATCH 08/23] chore(stdlib): remove print statements --- lib/src/bindings.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index 185c061..d6da14e 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -86,16 +86,12 @@ class RuntimeBinding { ); } - print('Calling binding: $name'); - var result = await Function.apply( function, positionalArgs, namedArgs, ); - print('Binding $name returned: $result'); - final resultType = $Type.fromValue(result); if (resultType.canCast(returnType)) { From 8416483fc22c1429b6656e1cb81e22877d79ef13 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Wed, 18 Jun 2025 05:54:49 +0200 Subject: [PATCH 09/23] feat(stdlib): implement http bindings --- bin/dscript.dart | 11 +- bin/test.dscript | 7 +- lib/src/bindings.dart | 15 +- lib/src/permissions.dart | 7 +- lib/src/stdlib/http.dart | 323 +++++++++++++++++++++++++++++++++++++ lib/src/stdlib/stdlib.dart | 3 + lib/src/types.dart | 26 ++- pubspec.lock | 16 ++ pubspec.yaml | 1 + 9 files changed, 394 insertions(+), 15 deletions(-) create mode 100644 lib/src/stdlib/http.dart diff --git a/bin/dscript.dart b/bin/dscript.dart index c73f5af..1e7441e 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -20,6 +20,14 @@ void main(List arguments) async { ); }); + HttpBindings.getBinding.addPreMiddleware(({ + required RuntimeBinding binding, + required List positionalArgs, + required Map namedArgs, + }) { + print('Sending GET request to ${positionalArgs[0]}'); + }); + final code = await InputStream.fromPath('./bin/test.dscript'); final script = analyze( @@ -89,9 +97,6 @@ void main(List arguments) async { args: {'foo': 42}, )); - print(bytecode.implementations['randomString']?.toDebugString() ?? - 'No implementation found for randomString'); - print(await runtime.run( 'randomString', )); diff --git a/bin/test.dscript b/bin/test.dscript index a772b3f..d23992b 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -38,13 +38,14 @@ contract Random { } - impl randomString() -> string { - try{ + impl randomString() -> string { + try { log::info("Reading file: " + fs::absolute(srcFile)); final contents = fs::read(srcFile); log::info("Successfully read file"); + return contents; - }catch(e) { + } catch(e) { log::error("Error reading file", error: e); return "Error"; } diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index d6da14e..516cbde 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -31,7 +31,9 @@ class RuntimeBinding { final String? description; /// The return type of the function as a dsl type. - $Type get returnType => $Type.from(T.toString()); + $Type get returnType => _returnType ?? $Type.from(T.toString()); + + final $Type? _returnType; /// A list of pre-binding middlewares that are called before the binding's function is executed. /// @@ -44,6 +46,12 @@ class RuntimeBinding { final List> _postMiddlewares = []; /// Creates a new [RuntimeBinding] instance. + /// + /// 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, @@ -51,7 +59,8 @@ class RuntimeBinding { this.permissions = const [], this.positionalParams = const {}, required this.description, - }); + $Type? returnType, + }) : _returnType = returnType; /// Adds a [PreBindingMiddleware] to this binding called in the order it was added. /// @@ -99,7 +108,7 @@ class RuntimeBinding { result = resultType.cast(returnType, result) as T; } else { throw StateError( - 'Invalid return type: expected $T, got ${result.runtimeType}', + 'Invalid return type: expected $returnType, got $resultType', ); } diff --git a/lib/src/permissions.dart b/lib/src/permissions.dart index 5642ab1..d8c2d13 100644 --- a/lib/src/permissions.dart +++ b/lib/src/permissions.dart @@ -29,11 +29,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/stdlib/http.dart b/lib/src/stdlib/http.dart new file mode 100644 index 0000000..848bedf --- /dev/null +++ b/lib/src/stdlib/http.dart @@ -0,0 +1,323 @@ +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, + }; + + /// Maps a [Dio] [Response] to it's dsl type representation. + static Map mapResponse(Response response) { + return { + $Type.structKey: Struct.httpResponse.name, + 'statusCode': response.statusCode, + 'data': response.data.toString(), + 'headers': response.headers.map, + 'isRedirect': response.isRedirect, + 'statusMessage': response.statusMessage, + }; + } + + /// 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 mapResponse(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 mapResponse(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 mapResponse(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 mapResponse(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 mapResponse(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 mapResponse(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 mapResponse(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.', + ); +} + +/// Typedef for the [Struct.httpResponse] type. +typedef HttpResponse = Map; diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 396c94b..4a47c23 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -2,6 +2,7 @@ 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:pub_semver/pub_semver.dart'; @@ -13,6 +14,7 @@ part 'fs.dart'; part 'list.dart'; part 'map.dart'; part 'dynamic.dart'; +part 'http.dart'; /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math @@ -100,6 +102,7 @@ $name { const ListBindings(), const MapBindings(), const DynamicBindings(), + const HttpBindings(), LogBindings(metadata), ]; } diff --git a/lib/src/types.dart b/lib/src/types.dart index 8e6c630..8a56612 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -226,6 +226,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, @@ -655,8 +663,24 @@ class Struct extends $Type { description: 'Represents an error with a message and stack trace.', ); + /// Standard HTTP response struct used in Dscript. + static final httpResponse = Struct( + name: 'HttpResponse', + fields: { + 'statusCode': PrimitiveType.INT, + 'headers': MapType( + keyType: PrimitiveType.STRING, + valueType: PrimitiveType.STRING, + ), + 'body': PrimitiveType.STRING.asNullable(), + }, + nullable: false, + description: + 'Represents an HTTP response with status code, headers, and body.', + ); + /// Default structs defined within the language. - static final defaults = [error]; + static final defaults = [error, httpResponse]; } /// Signature of a contract. diff --git a/pubspec.lock b/pubspec.lock index bd8db85..d1442f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -89,6 +89,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" equatable: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 523d286..b7ad475 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,7 @@ environment: dependencies: antlr4: ^4.13.2 collection: ^1.19.1 + dio: ^5.8.0+1 equatable: ^2.0.7 logging: ^1.3.0 meta: ^1.17.0 From 228865f7ef12dc00fe3d94c85c9b848bbbbeec83 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Wed, 18 Jun 2025 06:14:18 +0200 Subject: [PATCH 10/23] fix(compiler): make for in loops work --- bin/dscript.dart | 2 ++ bin/test.dscript | 24 +++++++++++++++--------- lib/src/analyzer/visitors/flow.dart | 20 +++++++++----------- lib/src/compiler/naivie_compiler.dart | 7 +++---- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/bin/dscript.dart b/bin/dscript.dart index 1e7441e..a0c6afe 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -97,6 +97,8 @@ void main(List arguments) async { args: {'foo': 42}, )); + print(bytecode.implementations['randomString']!.toDebugString()); + print(await runtime.run( 'randomString', )); diff --git a/bin/test.dscript b/bin/test.dscript index d23992b..683daed 100644 --- a/bin/test.dscript +++ b/bin/test.dscript @@ -39,16 +39,22 @@ contract Random { impl randomString() -> string { - 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"; + for (final int i in [1, 2, 3, 4, 5]) { + log::info("Iteration: " + i); } + + ///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"; } diff --git a/lib/src/analyzer/visitors/flow.dart b/lib/src/analyzer/visitors/flow.dart index 5efc06b..8afb6eb 100644 --- a/lib/src/analyzer/visitors/flow.dart +++ b/lib/src/analyzer/visitors/flow.dart @@ -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/compiler/naivie_compiler.dart b/lib/src/compiler/naivie_compiler.dart index cd194a7..b6069f2 100644 --- a/lib/src/compiler/naivie_compiler.dart +++ b/lib/src/compiler/naivie_compiler.dart @@ -236,10 +236,6 @@ class NaiveCompiler extends Compiler { startLoop(); - // Condition: index < iterable.length - emit(Instruction.read, indexTemp.frame, indexTemp.index); - emit(Instruction.read, iterableTemp.frame, iterableTemp.index); - // push iter on the stack. emit(Instruction.read, iterableTemp.frame, iterableTemp.index); @@ -252,6 +248,9 @@ class NaiveCompiler extends Compiler { 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); From b648a35c60e3665588cf5050ddc4e4e73d0ea7c0 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Wed, 18 Jun 2025 18:37:10 +0200 Subject: [PATCH 11/23] docs(stdlib): add documentation for customizing the stdlib --- bin/doc.dart | 21 +++-- bin/dscript.dart | 6 +- docs/_sidebar.md | 1 + docs/advanced/middlewares.md | 14 +-- docs/advanced/runtimes.md | 2 + docs/advanced/stdlib.md | 30 +++++++ docs/language/standard-library.md | 140 ++++++++++++++++++++++++++++-- lib/src/stdlib/log.dart | 3 +- 8 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 docs/advanced/stdlib.md diff --git a/bin/doc.dart b/bin/doc.dart index 845dd8f..a554494 100644 --- a/bin/doc.dart +++ b/bin/doc.dart @@ -21,9 +21,14 @@ void main() async { 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('| ${glob.key} | `${glob.value.$2}` | ${glob.value.$1} |'); } sb.writeln(); @@ -54,7 +59,7 @@ String _documentLibrary(LibraryBinding lib) { sb.writeln('## ${lib.name}'); sb.writeln(); - sb.writeln(lib.description); + sb.writeln(lib.description.docstring); sb.writeln(); for (final binding in lib.bindings) { @@ -100,10 +105,7 @@ String _documentBinding(RuntimeBinding binding) { } if (binding.description != null && binding.description!.isNotEmpty) { - final description = binding.description!.trim().replaceAllMapped( - RegExp(r'\[([^\]]+)\]'), - (m) => '${m.group(1)}', - ); + final description = binding.description!.docstring; if (description.contains('\n')) { final summary = description.split('\n').first; @@ -128,3 +130,10 @@ String _documentBinding(RuntimeBinding binding) { 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 a0c6afe..a8e07df 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -21,9 +21,9 @@ void main(List arguments) async { }); HttpBindings.getBinding.addPreMiddleware(({ - required RuntimeBinding binding, - required List positionalArgs, - required Map namedArgs, + required binding, + required positionalArgs, + required namedArgs, }) { print('Sending GET request to ${positionalArgs[0]}'); }); diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 0691f9c..bb83e50 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -14,3 +14,4 @@ - [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/middlewares.md b/docs/advanced/middlewares.md index 7d56ebf..877316a 100644 --- a/docs/advanced/middlewares.md +++ b/docs/advanced/middlewares.md @@ -16,9 +16,9 @@ Runs *before* the bound function. Use these to tweak or validate inputs. If one ```dart // Example: log reads and block secret files FsBindings.readFileBinding.addPreMiddleware(({ - required RuntimeBinding binding, - required List positionalArgs, - required Map namedArgs, + required binding, + required positionalArgs, + required namedArgs, }) { final path = positionalArgs[0] as String; print('Reading file $path'); @@ -38,10 +38,10 @@ Runs *after* the bound function. Use these to inspect or transform outputs befor ```dart // Example: redact secret files in the response FsBindings.readFileBinding.addPostMiddleware(({ - required RuntimeBinding binding, - required List positionalArgs, - required Map namedArgs, - required dynamic result, + required binding, + required positionalArgs, + required namedArgs, + required result, }) { final path = positionalArgs[0] as String; if (path == 'secret-file.txt') { diff --git a/docs/advanced/runtimes.md b/docs/advanced/runtimes.md index 5c75596..7695137 100644 --- a/docs/advanced/runtimes.md +++ b/docs/advanced/runtimes.md @@ -12,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/language/standard-library.md b/docs/language/standard-library.md index 5d35c9e..1535bc9 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -7,20 +7,25 @@ - [list](#list) - [map](#map) - [dynamic](#dynamic) +- [http](#http) - [log](#log) --- ## Globals The following globals are available in the dscript runtime. They are not part of any library. -- `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)* + + +| 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 | --- @@ -952,12 +957,131 @@ Various utilities for dynamic values. +--- + +## 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` + + + + --- ## log Logging utilities. +!> Log messages will not be visible when using IsolateRuntime. + ### info → `void` | Name | Type | Kind | diff --git a/lib/src/stdlib/log.dart b/lib/src/stdlib/log.dart index 23eb523..a037c68 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -10,7 +10,8 @@ class LogBindings extends LibraryBinding { LogBindings(ScriptMetadata metadata) : super( name: 'log', - description: 'Logging utilities.', + description: + 'Logging utilities.\n\n!> Log messages will not be visible when using [IsolateRuntime].', ) { logger = Logger( '[Dscript] ${metadata.author}.${metadata.name}@${metadata.version}', From 7674bd1b409baeead5d4742e125bbdbee1a83867 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Wed, 18 Jun 2025 18:48:38 +0200 Subject: [PATCH 12/23] feat(stdlib): implement json & utf8 lib --- docs/language/standard-library.md | 60 +++++++++++++++++++++++++++++++ lib/src/stdlib/json.dart | 44 +++++++++++++++++++++++ lib/src/stdlib/stdlib.dart | 5 +++ lib/src/stdlib/utf8.dart | 41 +++++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 lib/src/stdlib/json.dart create mode 100644 lib/src/stdlib/utf8.dart diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index 1535bc9..03b078c 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -8,6 +8,8 @@ - [map](#map) - [dynamic](#dynamic) - [http](#http) +- [json](#json) +- [utf8](#utf8) - [log](#log) --- @@ -1074,6 +1076,64 @@ Library for making HTTP requests. +--- + +## json + +Provides JSON encoding and decoding functions. + +### decode → `Map` + +| Name | Type | Kind | +| --- | --- | --- | +| str | `string` | Positional (1) | +
+Deserializes the given str to a Map. +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. + + + + --- ## log diff --git a/lib/src/stdlib/json.dart b/lib/src/stdlib/json.dart new file mode 100644 index 0000000..bfb5d21 --- /dev/null +++ b/lib/src/stdlib/json.dart @@ -0,0 +1,44 @@ +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 [Map]. + + Throws an error if the string is not valid JSON or cannot be parsed. + ''', + function: (String str) { + return jsonDecode(str); + }, + 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(), + }, + ); +} diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index 4a47c23..bb7e97b 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -1,4 +1,5 @@ // coverage:ignore-file +import 'dart:convert'; import 'dart:io'; import 'dart:math'; @@ -15,6 +16,8 @@ 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 @@ -103,6 +106,8 @@ $name { const MapBindings(), const DynamicBindings(), const HttpBindings(), + const JsonBindings(), + const Utf8Bindings(), LogBindings(metadata), ]; } diff --git a/lib/src/stdlib/utf8.dart b/lib/src/stdlib/utf8.dart new file mode 100644 index 0000000..d6c9a07 --- /dev/null +++ b/lib/src/stdlib/utf8.dart @@ -0,0 +1,41 @@ +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), + }, + ); + + /// 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, + }, + ); +} From 56a3d6872ac1be921a670dafa4b9db957b450f4b Mon Sep 17 00:00:00 2001 From: mcquenji Date: Wed, 18 Jun 2025 19:05:00 +0200 Subject: [PATCH 13/23] ci(docs): auto upddate docs --- .github/workflows/docs.yaml | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/docs.yaml 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 From bd7f83ebd25383bf55a678ed43bba2a789282234 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Thu, 19 Jun 2025 19:17:23 +0200 Subject: [PATCH 14/23] docs: add motivation to readme --- README.md | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) 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 From fde3a1a15942b7fd7be070b54e6e832401221946 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Fri, 20 Jun 2025 15:40:09 +0200 Subject: [PATCH 15/23] feat(stdlib): implement base64 bindings --- bin/doc.dart | 23 ++++++++++++++++ docs/language/standard-library.md | 37 +++++++++++++++++++++++-- lib/src/stdlib/base64.dart | 37 +++++++++++++++++++++++++ lib/src/stdlib/json.dart | 15 ++++++++-- lib/src/stdlib/stdlib.dart | 2 ++ lib/src/types.dart | 17 +++++++++++- pubspec.lock | 46 +++++++++++++++---------------- 7 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 lib/src/stdlib/base64.dart diff --git a/bin/doc.dart b/bin/doc.dart index a554494..5bbaa1f 100644 --- a/bin/doc.dart +++ b/bin/doc.dart @@ -33,6 +33,29 @@ void main() async { 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(); diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index 03b078c..bac802b 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -29,6 +29,39 @@ The following globals are available in the dscript runtime. They are not part of | 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` | +| body | `string?` | + +### JSON + +Result of json::decode. It's either a Map or a List. + +| Field | Type | +| --- | --- | +| map | `Map?` | +| list | `List?` | +| isMap | `bool` | +| isList | `bool` | + + --- @@ -1082,13 +1115,13 @@ Library for making HTTP requests. Provides JSON encoding and decoding functions. -### decode → `Map` +### decode → `JSON` | Name | Type | Kind | | --- | --- | --- | | str | `string` | Positional (1) |
-Deserializes the given str to a Map. +Deserializes the given str to a JSON object. Throws an error if the string is not valid JSON or cannot be parsed.

diff --git a/lib/src/stdlib/base64.dart b/lib/src/stdlib/base64.dart new file mode 100644 index 0000000..73666d8 --- /dev/null +++ b/lib/src/stdlib/base64.dart @@ -0,0 +1,37 @@ +part of 'stdlib.dart'; + +/// [base64Encode] and [base64Decode] bindings. +class Base64Bindings extends LibraryBinding { + /// Base64 encoding and decoding functions. + const Base64Bindings() + : super( + name: 'base64', + description: 'Base64 encoding and decoding functions.', + ); + + /// [base64Encode] binding. + static final encodeBinding = RuntimeBinding( + name: 'encode', + description: '''Encodes the given [str] to a Base64 string.''', + function: (String str) { + return base64Encode(utf8.encode(str)); + }, + positionalParams: {'str': PrimitiveType.STRING}, + ); + + /// [base64Decode] binding. + static final decodeBinding = RuntimeBinding( + name: 'decode', + description: '''Decodes the given Base64 [str] to a string.''', + function: (String str) { + return utf8.decode(base64Decode(str)); + }, + positionalParams: {'str': PrimitiveType.STRING}, + ); + + @override + Set get bindings => { + encodeBinding, + decodeBinding, + }; +} diff --git a/lib/src/stdlib/json.dart b/lib/src/stdlib/json.dart index bfb5d21..d0da75a 100644 --- a/lib/src/stdlib/json.dart +++ b/lib/src/stdlib/json.dart @@ -16,15 +16,24 @@ class JsonBindings extends LibraryBinding { }; /// Binding for [jsonDecode]. - static final decodeBinding = RuntimeBinding>( + static final decodeBinding = RuntimeBinding( name: 'decode', - description: '''Deserializes the given [str] to a [Map]. + 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) { - return jsonDecode(str); + final json = jsonDecode(str); + + return { + 'map': json is Map ? json : null, + 'list': json is List ? json : null, + 'isMap': json is Map, + 'isList': json is List, + $Type.structKey: Struct.json.name, + }; }, + returnType: Struct.json, positionalParams: {'str': PrimitiveType.STRING}, ); diff --git a/lib/src/stdlib/stdlib.dart b/lib/src/stdlib/stdlib.dart index bb7e97b..c7fa762 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -18,6 +18,7 @@ part 'dynamic.dart'; part 'http.dart'; part 'json.dart'; part 'utf8.dart'; +part 'base64.dart'; /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math @@ -108,6 +109,7 @@ $name { const HttpBindings(), const JsonBindings(), const Utf8Bindings(), + const Base64Bindings(), LogBindings(metadata), ]; } diff --git a/lib/src/types.dart b/lib/src/types.dart index 8a56612..7cff593 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -679,8 +679,23 @@ class Struct extends $Type { '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, + }, + ); + /// Default structs defined within the language. - static final defaults = [error, httpResponse]; + static final defaults = [error, httpResponse, json]; } /// Signature of a contract. diff --git a/pubspec.lock b/pubspec.lock index d1442f2..beed6b3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: analyzer - sha256: f4c21c94eb4623b183c1014a470196b3910701bea9b926e6c91270d756e6fc60 + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" url: "https://pub.dev" source: hosted - version: "7.4.1" + version: "7.4.5" antlr4: dependency: "direct main" description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: coverage - sha256: "9086475ef2da7102a0c0a4e37e1e30707e7fb7b6d28c209f559a9c5f8ce42016" + sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.14.1" crypto: dependency: transitive description: @@ -165,18 +165,18 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" 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: @@ -253,10 +253,10 @@ packages: 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" shelf: dependency: transitive description: @@ -349,26 +349,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.11" typed_data: dependency: transitive description: @@ -381,18 +381,18 @@ packages: 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: @@ -405,10 +405,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: @@ -434,4 +434,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.4 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" From 3642ba696a746ce27b18e188367eb7fac0699517 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 23 Jun 2025 19:47:28 +0200 Subject: [PATCH 16/23] feat(stdlib): add more collection utils Closes: #25 #24 #23 --- lib/src/stdlib/list.dart | 11 +++++++ lib/src/stdlib/map.dart | 62 ++++++++++++++++++++++++++++++++++++++-- lib/src/types.dart | 10 +++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/lib/src/stdlib/list.dart b/lib/src/stdlib/list.dart index fccb1c3..30ab4d7 100644 --- a/lib/src/stdlib/list.dart +++ b/lib/src/stdlib/list.dart @@ -25,6 +25,7 @@ class ListBindings extends LibraryBinding { indexOfBinding, lastIndexOfBinding, containsBinding, + copyBinding, }; /// Binding for [List.length]. @@ -188,4 +189,14 @@ class ListBindings extends LibraryBinding { }, description: 'Returns true if the list contains [element].', ); + + /// 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].', + ); } diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart index 841ea18..f98b895 100644 --- a/lib/src/stdlib/map.dart +++ b/lib/src/stdlib/map.dart @@ -22,6 +22,9 @@ class MapBindings extends LibraryBinding { clearBinding, removeBinding, keyOfBinding, + keysOfBinding, + entriesBinding, + copyBinding, }; /// Binding for [Map.length]. @@ -164,7 +167,7 @@ class MapBindings extends LibraryBinding { 'Removes the key-value pair for the specified [key] from the map.', ); - /// B + /// Returns the first key associated with the specified [value]. static final keyOfBinding = RuntimeBinding( name: 'keyOf', function: (Map map, dynamic value) { @@ -182,6 +185,61 @@ class MapBindings extends LibraryBinding { ), 'value': const DynamicType(), }, - description: 'Returns the key associated with the specified [value].', + description: 'Returns the first key associated with the specified [value].', + ); + + /// 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].', + ); + + /// [Map.entries] binding. + static final entriesBinding = RuntimeBinding>>( + name: 'entries', + function: (Map map) => map.entries.map((entry) { + return { + 'key': entry.key, + 'value': entry.value, + $Type.structKey: Struct.mapEntry.name, + }; + }).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].', ); } diff --git a/lib/src/types.dart b/lib/src/types.dart index 7cff593..9ee6ad3 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -694,6 +694,16 @@ class Struct extends $Type { }, ); + /// Represents a key-value pair in a map. + static final mapEntry = const Struct( + name: 'MapEntry', + description: 'Represents a key-value pair in a map.', + fields: { + 'key': DynamicType(), + 'value': DynamicType(), + }, + ); + /// Default structs defined within the language. static final defaults = [error, httpResponse, json]; } From 137afee0a2145e328ef9fb1f9f3bd78a6c6caeac Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 11 Aug 2025 17:09:53 +0200 Subject: [PATCH 17/23] fix!: refrain from type inference by name BREAKING: Made `returnType` required for bindings BREAKING: Added a custom lint rule to ban `toString()` on any instances of `Type` --- analysis_options.yaml | 2 + custom_lints/.gitignore | 7 ++ custom_lints/README.md | 7 ++ custom_lints/analysis_options.yaml | 30 +++++ custom_lints/lib/custom_lints.dart | 53 +++++++++ custom_lints/pubspec.yaml | 14 +++ lib/src/bindings.dart | 8 +- lib/src/compiler/instructions.dart | 14 +-- lib/src/contract_builder.dart | 13 +++ lib/src/stdlib/base64.dart | 2 + lib/src/stdlib/dynamic.dart | 6 + lib/src/stdlib/fs.dart | 16 +++ lib/src/stdlib/json.dart | 1 + lib/src/stdlib/list.dart | 15 +++ lib/src/stdlib/log.dart | 7 ++ lib/src/stdlib/map.dart | 18 ++- lib/src/stdlib/math.dart | 35 ++++-- lib/src/stdlib/string.dart | 16 +++ lib/src/stdlib/utf8.dart | 2 + lib/src/types.dart | 35 +++++- pubspec.lock | 181 ++++++++++++++++++++++++++--- pubspec.yaml | 3 + 22 files changed, 446 insertions(+), 39 deletions(-) create mode 100644 custom_lints/.gitignore create mode 100644 custom_lints/README.md create mode 100644 custom_lints/analysis_options.yaml create mode 100644 custom_lints/lib/custom_lints.dart create mode 100644 custom_lints/pubspec.yaml 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/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/lib/src/bindings.dart b/lib/src/bindings.dart index 516cbde..4df4543 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -31,9 +31,7 @@ class RuntimeBinding { final String? description; /// The return type of the function as a dsl type. - $Type get returnType => _returnType ?? $Type.from(T.toString()); - - final $Type? _returnType; + final $Type returnType; /// A list of pre-binding middlewares that are called before the binding's function is executed. /// @@ -59,8 +57,8 @@ class RuntimeBinding { this.permissions = const [], this.positionalParams = const {}, required this.description, - $Type? returnType, - }) : _returnType = returnType; + required this.returnType, + }); /// Adds a [PreBindingMiddleware] to this binding called in the order it was added. /// 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/contract_builder.dart b/lib/src/contract_builder.dart index 3f71758..deb9d62 100644 --- a/lib/src/contract_builder.dart +++ b/lib/src/contract_builder.dart @@ -92,6 +92,7 @@ class BindingBuilder { final Function _function; final Map _params = {}; final Map _namedParams = {}; + $Type? _returnType; String _description = ''; /// Internal constructor; typically obtained via [ContractSignatureBuilder.bind]. @@ -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,6 +144,11 @@ 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, @@ -144,6 +156,7 @@ class BindingBuilder { positionalParams: Map.unmodifiable(_params), namedParams: Map.unmodifiable(_namedParams), description: _description.isNotEmpty ? _description : null, + returnType: _returnType!, ); } } diff --git a/lib/src/stdlib/base64.dart b/lib/src/stdlib/base64.dart index 73666d8..46424fd 100644 --- a/lib/src/stdlib/base64.dart +++ b/lib/src/stdlib/base64.dart @@ -17,6 +17,7 @@ class Base64Bindings extends LibraryBinding { return base64Encode(utf8.encode(str)); }, positionalParams: {'str': PrimitiveType.STRING}, + returnType: PrimitiveType.STRING, ); /// [base64Decode] binding. @@ -27,6 +28,7 @@ class Base64Bindings extends LibraryBinding { return utf8.decode(base64Decode(str)); }, positionalParams: {'str': PrimitiveType.STRING}, + returnType: PrimitiveType.STRING, ); @override diff --git a/lib/src/stdlib/dynamic.dart b/lib/src/stdlib/dynamic.dart index 900c255..8591499 100644 --- a/lib/src/stdlib/dynamic.dart +++ b/lib/src/stdlib/dynamic.dart @@ -27,6 +27,7 @@ class DynamicBindings extends LibraryBinding { 'value': const DynamicType(), }, description: 'Converts [value] to a string.', + returnType: PrimitiveType.STRING, ); /// Binding for converting a dynamic value to an int. @@ -38,6 +39,7 @@ class DynamicBindings extends LibraryBinding { 'value': const DynamicType(), }, description: 'Converts [value] to an int.', + returnType: PrimitiveType.INT, ); /// Binding for converting a dynamic value to a double. @@ -49,6 +51,7 @@ class DynamicBindings extends LibraryBinding { 'value': const DynamicType(), }, description: 'Converts [value] to a double.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for converting a dynamic value to a bool. @@ -60,6 +63,7 @@ class DynamicBindings extends LibraryBinding { '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. @@ -81,6 +85,7 @@ class DynamicBindings extends LibraryBinding { }, 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. @@ -91,5 +96,6 @@ class DynamicBindings extends LibraryBinding { '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 77ba79a..460f748 100644 --- a/lib/src/stdlib/fs.dart +++ b/lib/src/stdlib/fs.dart @@ -44,6 +44,7 @@ class FsBindings extends LibraryBinding { }, 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. @@ -64,6 +65,7 @@ class FsBindings extends LibraryBinding { 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. @@ -82,6 +84,7 @@ class FsBindings extends LibraryBinding { }, permissions: [ScriptPermission.writeFiles], description: 'Deletes a file at the given [path].', + returnType: PrimitiveType.VOID, ); /// Checks if a file exists at the given path. @@ -96,6 +99,7 @@ class FsBindings extends LibraryBinding { }, permissions: [ScriptPermission.readFiles], description: 'Checks if a file exists at the given [path].', + returnType: PrimitiveType.BOOL, ); /// Lists the contents of a directory. @@ -118,6 +122,7 @@ class FsBindings extends LibraryBinding { 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. @@ -136,6 +141,7 @@ class FsBindings extends LibraryBinding { 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. @@ -149,6 +155,7 @@ class FsBindings extends LibraryBinding { }, permissions: [ScriptPermission.readFiles], description: 'Checks if the path is a directory.', + returnType: PrimitiveType.BOOL, ); /// Checks if the path is a file. @@ -162,6 +169,7 @@ class FsBindings extends LibraryBinding { }, permissions: [ScriptPermission.readFiles], description: 'Checks if the path is a file.', + returnType: PrimitiveType.BOOL, ); /// Gets the absolute path of a file or directory. @@ -177,6 +185,7 @@ class FsBindings extends LibraryBinding { 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. @@ -185,6 +194,7 @@ class FsBindings extends LibraryBinding { 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. @@ -203,6 +213,7 @@ class FsBindings extends LibraryBinding { }, 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. @@ -221,6 +232,7 @@ class FsBindings extends LibraryBinding { }, 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. @@ -238,6 +250,7 @@ class FsBindings extends LibraryBinding { }, 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. @@ -257,6 +270,7 @@ class FsBindings extends LibraryBinding { }, 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. @@ -276,6 +290,7 @@ class FsBindings extends LibraryBinding { 'path': PrimitiveType.STRING, }, description: 'Gets the base name of the given [path].', + returnType: PrimitiveType.STRING, ); /// Gets the directory name of a file or directory. @@ -294,5 +309,6 @@ Finds the final path component of a path, using the platform's path separator to 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/json.dart b/lib/src/stdlib/json.dart index d0da75a..e109894 100644 --- a/lib/src/stdlib/json.dart +++ b/lib/src/stdlib/json.dart @@ -49,5 +49,6 @@ class JsonBindings extends LibraryBinding { positionalParams: { 'obj': const DynamicType(), }, + returnType: PrimitiveType.STRING, ); } diff --git a/lib/src/stdlib/list.dart b/lib/src/stdlib/list.dart index 30ab4d7..acd786f 100644 --- a/lib/src/stdlib/list.dart +++ b/lib/src/stdlib/list.dart @@ -36,6 +36,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Returns the number of elements in the list.', + returnType: PrimitiveType.INT, ); /// Binding for [List.isEmpty]. @@ -46,6 +47,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Returns true if the list is empty.', + returnType: PrimitiveType.BOOL, ); /// Binding for [List.isNotEmpty]. @@ -56,6 +58,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Returns true if the list is not empty.', + returnType: PrimitiveType.BOOL, ); /// Binding for [List.add]. @@ -66,6 +69,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Adds [element] to the end of the [list].', + returnType: PrimitiveType.VOID, ); /// Binding for [List.addAll]. @@ -78,6 +82,7 @@ class ListBindings extends LibraryBinding { 'elements': ListType(elementType: const DynamicType()), }, description: 'Adds all [elements] to the end of the [list].', + returnType: PrimitiveType.VOID, ); /// Binding for [List.clear]. @@ -88,6 +93,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Removes all elements from the list.', + returnType: PrimitiveType.VOID, ); /// Binding for [List.remove]. @@ -98,6 +104,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Removes the first occurrence of [element] from the list.', + returnType: PrimitiveType.BOOL, ); /// Binding for [List.removeAt]. @@ -109,6 +116,7 @@ class ListBindings extends LibraryBinding { 'index': PrimitiveType.INT, }, description: 'Removes and returns the element at [index] from the list.', + returnType: const DynamicType(), ); /// Binding for [List.removeLast]. @@ -119,6 +127,7 @@ class ListBindings extends LibraryBinding { 'list': ListType(elementType: const DynamicType()), }, description: 'Removes and returns the last element from the list.', + returnType: const DynamicType(), ); /// Binding for [List.insert]. @@ -132,6 +141,7 @@ class ListBindings extends LibraryBinding { 'element': const DynamicType(), }, description: 'Inserts [element] at [index] in the list.', + returnType: PrimitiveType.VOID, ); /// Binding for [List.insertAll]. @@ -145,6 +155,7 @@ class ListBindings extends LibraryBinding { 'elements': ListType(elementType: const DynamicType()), }, description: 'Inserts all [elements] at [index] in the list.', + returnType: PrimitiveType.VOID, ); /// Binding for [List.indexOf]. @@ -161,6 +172,7 @@ class ListBindings extends LibraryBinding { }, description: 'Returns the index of the first occurrence of [element] in the list, starting from [start].', + returnType: PrimitiveType.INT, ); /// Binding for [List.lastIndexOf]. @@ -177,6 +189,7 @@ class ListBindings extends LibraryBinding { }, description: 'Returns the index of the last occurrence of [element] in the list, starting from [start].', + returnType: PrimitiveType.INT, ); /// Binding for [List.contains]. @@ -188,6 +201,7 @@ class ListBindings extends LibraryBinding { 'element': const DynamicType(), }, description: 'Returns true if the list contains [element].', + returnType: PrimitiveType.BOOL, ); /// Binding for [List.from]. @@ -198,5 +212,6 @@ class ListBindings extends LibraryBinding { '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 a037c68..21cfea7 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -40,6 +40,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs an info message.', + returnType: PrimitiveType.VOID, ); /// Binding for warning logging. @@ -54,6 +55,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs a warning message.', + returnType: PrimitiveType.VOID, ); /// Binding for error logging. @@ -69,6 +71,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs an error message.', + returnType: PrimitiveType.VOID, ); /// Binding for debug logging. @@ -82,6 +85,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs a debug message.', + returnType: PrimitiveType.VOID, ); /// Binding for verbose logging. @@ -96,6 +100,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs a verbose message.', + returnType: PrimitiveType.VOID, ); /// Binding for fatal logging. @@ -110,6 +115,7 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs a fatal message.', + returnType: PrimitiveType.VOID, ); /// Binding for critical logging. @@ -124,5 +130,6 @@ class LogBindings extends LibraryBinding { #error: const DynamicType(), }, description: 'Logs a critical message.', + returnType: PrimitiveType.VOID, ); } diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart index f98b895..11fcb52 100644 --- a/lib/src/stdlib/map.dart +++ b/lib/src/stdlib/map.dart @@ -38,6 +38,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Returns the number of key-value pairs in the map.', + returnType: PrimitiveType.INT, ); /// Binding for [Map.isEmpty]. @@ -51,6 +52,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Returns true if the map is empty.', + returnType: PrimitiveType.BOOL, ); /// Binding for [Map.isNotEmpty]. @@ -64,6 +66,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Returns true if the map is not empty.', + returnType: PrimitiveType.BOOL, ); /// Binding for [Map.containsKey]. @@ -78,6 +81,7 @@ class MapBindings extends LibraryBinding { 'key': const DynamicType(), }, description: 'Returns true if the map contains the specified [key].', + returnType: PrimitiveType.BOOL, ); /// Binding for [Map.containsValue]. @@ -93,6 +97,7 @@ class MapBindings extends LibraryBinding { 'value': const DynamicType(), }, description: 'Returns true if the map contains the specified [value].', + returnType: PrimitiveType.BOOL, ); /// Binding for [Map.keys]. @@ -106,6 +111,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Returns a list of all keys in the map.', + returnType: ListType(elementType: const DynamicType()), ); /// Binding for [Map.values]. @@ -119,6 +125,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Returns a list of all values in the map.', + returnType: ListType(elementType: const DynamicType()), ); /// Binding for [Map.addAll]. @@ -137,6 +144,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Adds all key-value pairs from [other] to the map.', + returnType: PrimitiveType.VOID, ); /// Binding for [Map.clear]. @@ -150,6 +158,7 @@ class MapBindings extends LibraryBinding { ), }, description: 'Removes all key-value pairs from the map.', + returnType: PrimitiveType.VOID, ); /// Binding for [Map.remove]. @@ -165,6 +174,7 @@ class MapBindings extends LibraryBinding { }, 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]. @@ -186,6 +196,7 @@ class MapBindings extends LibraryBinding { '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]. @@ -206,10 +217,11 @@ class MapBindings extends LibraryBinding { }, description: 'Returns a list of keys associated with the specified [value].', + returnType: ListType(elementType: const DynamicType()), ); /// [Map.entries] binding. - static final entriesBinding = RuntimeBinding>>( + static final entriesBinding = RuntimeBinding( name: 'entries', function: (Map map) => map.entries.map((entry) { return { @@ -241,5 +253,9 @@ class MapBindings extends LibraryBinding { ), }, 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 b1d2c76..5cf088a 100644 --- a/lib/src/stdlib/math.dart +++ b/lib/src/stdlib/math.dart @@ -43,6 +43,7 @@ class MathBindings extends LibraryBinding { }, description: 'Converts [x] to a [double] and returns the positive square root of the value', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [pow] function. @@ -80,6 +81,7 @@ 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. @@ -91,6 +93,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe }, description: 'Converts [x] to a [double] and returns the natural logarithm of the value.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [exp] function. @@ -102,6 +105,7 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe }, description: 'Converts [x] to a [double] and returns the natural exponent, [e], to the power [x].', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [sin] function. @@ -113,17 +117,20 @@ Notice that the result may overflow. If integers are represented as 64-bit numbe }, 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: { - 'x': 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( @@ -137,6 +144,7 @@ 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. @@ -148,6 +156,7 @@ The tangent function is equivalent to sin(x)/cos(x) }, description: 'Converts [x] to a [double] and returns its arc sine in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [acos] function. @@ -159,6 +168,7 @@ The tangent function is equivalent to sin(x)/cos(x) }, description: 'Converts [x] to a [double] and returns its arc cosine in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [atan] function. @@ -170,6 +180,7 @@ The tangent function is equivalent to sin(x)/cos(x) }, description: 'Converts [x] to a [double] and returns its arc tangent in radians.', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [atan2] function. @@ -193,6 +204,7 @@ 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. @@ -203,6 +215,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'x': PrimitiveType.NUM, }, description: 'Returns the absolute value of [x].', + returnType: PrimitiveType.DOUBLE, ); /// Binding for the [floor] function. @@ -213,6 +226,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'x': PrimitiveType.NUM, }, description: 'Returns the largest integer less than or equal to [x].', + returnType: PrimitiveType.INT, ); /// Binding for the [ceil] function. @@ -223,6 +237,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'x': PrimitiveType.NUM, }, description: 'Returns the smallest integer greater than or equal to [x].', + returnType: PrimitiveType.INT, ); /// Binding for the [round] function. @@ -233,6 +248,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'x': PrimitiveType.NUM, }, description: 'Rounds [x] number to the nearest integer.', + returnType: PrimitiveType.INT, ); /// Binding for the [clamp] function. @@ -248,6 +264,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'x': PrimitiveType.NUM, }, description: 'Clamps [x] number between a [min] and [max] value.', + returnType: PrimitiveType.NUM, ); /// Binding for the [min] function. @@ -259,6 +276,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'b': PrimitiveType.NUM, }, description: 'Returns the minimum of [a] and [b].', + returnType: PrimitiveType.NUM, ); /// Binding for the [max] function. @@ -270,6 +288,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'b': PrimitiveType.NUM, }, description: 'Returns the maximum of [a] and [b].', + returnType: PrimitiveType.NUM, ); /// Converts degrees to radians. @@ -280,6 +299,7 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'degrees': PrimitiveType.NUM, }, description: 'Converts [degrees] to radians.', + returnType: PrimitiveType.DOUBLE, ); /// Converts radians to degrees. @@ -290,5 +310,6 @@ If [a] is equal to zero, the vector ([b],[a]) is considered parallel to the x-ax 'radians': PrimitiveType.NUM, }, description: 'Converts [radians] to degrees.', + returnType: PrimitiveType.DOUBLE, ); } diff --git a/lib/src/stdlib/string.dart b/lib/src/stdlib/string.dart index 3c6c3bc..463c95d 100644 --- a/lib/src/stdlib/string.dart +++ b/lib/src/stdlib/string.dart @@ -38,6 +38,7 @@ class StringBindings extends LibraryBinding { 'code': PrimitiveType.INT, }, description: 'Creates a string from a single character code [code].', + returnType: PrimitiveType.STRING, ); /// Binding for [String.fromCharCodes]. @@ -48,6 +49,7 @@ class StringBindings extends LibraryBinding { 'codes': ListType(elementType: PrimitiveType.INT), }, description: 'Creates a string from a list of character codes [codes].', + returnType: PrimitiveType.STRING, ); /// Binding for [Object.toString]. @@ -59,6 +61,7 @@ class StringBindings extends LibraryBinding { }, 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]. @@ -69,6 +72,7 @@ class StringBindings extends LibraryBinding { 'str': PrimitiveType.STRING, }, description: 'Returns the length of [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.substring]. @@ -87,6 +91,7 @@ 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]. @@ -97,6 +102,7 @@ class StringBindings extends LibraryBinding { 'str': PrimitiveType.STRING, }, description: 'Converts [str] to uppercase.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.toLowerCase]. @@ -107,6 +113,7 @@ class StringBindings extends LibraryBinding { 'str': PrimitiveType.STRING, }, description: 'Converts [str] to lowercase.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.trim]. @@ -118,6 +125,7 @@ class StringBindings extends LibraryBinding { }, description: 'Removes leading and trailing whitespace from [str] and returns the resulting string.', + returnType: PrimitiveType.STRING, ); /// Binding for [String.split]. @@ -130,6 +138,7 @@ class StringBindings extends LibraryBinding { }, description: 'Splits [str] into a list of substrings using [pattern] as the delimiter.', + returnType: ListType(elementType: PrimitiveType.STRING), ); /// Binding for [String.replaceAll]. @@ -142,6 +151,7 @@ class StringBindings extends LibraryBinding { 'to': PrimitiveType.STRING, }, description: 'Replaces all occurrences of [from] with [to] in [str].', + returnType: PrimitiveType.STRING, ); /// Binding for [String.contains]. @@ -153,6 +163,7 @@ class StringBindings extends LibraryBinding { 'pattern': PrimitiveType.STRING, }, description: 'Returns true if [str] contains [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.startsWith]. @@ -165,6 +176,7 @@ class StringBindings extends LibraryBinding { }, description: 'Returns true if [str] starts with [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.endsWith]. @@ -176,6 +188,7 @@ class StringBindings extends LibraryBinding { 'pattern': PrimitiveType.STRING, }, description: 'Returns true if [str] ends with [pattern]. False otherwise.', + returnType: PrimitiveType.BOOL, ); /// Binding for [String.indexOf]. @@ -188,6 +201,7 @@ class StringBindings extends LibraryBinding { }, description: 'Returns the index of the first occurrence of [pattern] in [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.lastIndexOf]. @@ -200,6 +214,7 @@ class StringBindings extends LibraryBinding { }, description: 'Returns the index of the last occurrence of [pattern] in [str].', + returnType: PrimitiveType.INT, ); /// Binding for [String.replaceFirst]. @@ -213,5 +228,6 @@ class StringBindings extends LibraryBinding { '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 index d6c9a07..f5ac30f 100644 --- a/lib/src/stdlib/utf8.dart +++ b/lib/src/stdlib/utf8.dart @@ -25,6 +25,7 @@ class Utf8Bindings extends LibraryBinding { positionalParams: { 'bytes': ListType(elementType: PrimitiveType.INT), }, + returnType: PrimitiveType.STRING, ); /// Binding for [utf8Encode]. @@ -37,5 +38,6 @@ class Utf8Bindings extends LibraryBinding { positionalParams: { 'str': PrimitiveType.STRING, }, + returnType: ListType(elementType: PrimitiveType.INT), ); } diff --git a/lib/src/types.dart b/lib/src/types.dart index 9ee6ad3..ec94d11 100644 --- a/lib/src/types.dart +++ b/lib/src/types.dart @@ -15,6 +15,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, }; @@ -458,7 +460,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; @@ -466,7 +468,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, @@ -533,12 +535,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, @@ -704,6 +706,31 @@ class Struct extends $Type { }, ); + /// Represents [DateTime]. + static final dateTime = const Struct( + name: 'DateTime', + fields: { + 'year': PrimitiveType.INT, + 'month': PrimitiveType.INT, + 'day': PrimitiveType.INT, + 'hour': PrimitiveType.INT, + 'minute': PrimitiveType.INT, + 'second': PrimitiveType.INT, + }, + description: 'Represents a date and time.', + ); + + /// Represents [TimeOfDay]. + static final timeOfDay = const Struct( + name: 'TimeOfDay', + fields: { + 'hour': PrimitiveType.INT, + 'minute': PrimitiveType.INT, + 'second': PrimitiveType.INT, + }, + description: 'Represents a time of day.', + ); + /// Default structs defined within the language. static final defaults = [error, httpResponse, json]; } diff --git a/pubspec.lock b/pubspec.lock index beed6b3..4aed3a2 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: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c url: "https://pub.dev" source: hosted - version: "7.4.5" + 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,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + 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 +81,14 @@ 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" collection: dependency: "direct main" description: @@ -77,10 +109,10 @@ packages: dependency: transitive description: name: coverage - sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.14.1" + version: "1.15.0" crypto: dependency: transitive description: @@ -89,14 +121,61 @@ 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: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_web_adapter: dependency: transitive description: @@ -121,6 +200,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: @@ -137,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" http_multi_server: dependency: transitive description: @@ -169,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -249,6 +360,14 @@ 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: @@ -257,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted 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: @@ -313,6 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -329,6 +464,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: @@ -349,26 +492,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" typed_data: dependency: transitive description: @@ -377,6 +520,14 @@ 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: @@ -434,4 +585,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.7.2 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index b7ad475..0ff1a33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,5 +17,8 @@ dependencies: result_dart: ^2.1.0 dev_dependencies: + custom_lint: ^0.8.0 + custom_lints: + path: ./custom_lints lints: ^5.0.0 test: ^1.24.0 From 2509d98b8225fa9ecab0bdd78fc34aa7574a2699 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 11 Aug 2025 17:17:52 +0200 Subject: [PATCH 18/23] ci: prevent reporter from failing and fix fail checks --- .github/workflows/report.yaml | 7 ++++--- .github/workflows/tests.yaml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/report.yaml b/.github/workflows/report.yaml index bc78217..e94c2bf 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,8 @@ jobs: name: 🧪 Unit Tests path: test-results.json reporter: dart-json - fail-on-empty: true - fail-on-error: true + fail-on-empty: false + fail-on-error: false 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 From 35bf347f658e7eba77ef61ea0e8298c03953c63f Mon Sep 17 00:00:00 2001 From: mcquenji Date: Mon, 11 Aug 2025 17:20:37 +0200 Subject: [PATCH 19/23] ci: huh --- .github/workflows/report.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/report.yaml b/.github/workflows/report.yaml index e94c2bf..efc34d4 100644 --- a/.github/workflows/report.yaml +++ b/.github/workflows/report.yaml @@ -23,8 +23,6 @@ jobs: name: 🧪 Unit Tests path: test-results.json reporter: dart-json - fail-on-empty: false - fail-on-error: false max-annotations: 50 \ No newline at end of file From b73e6864790f9f7ae6549602d446e0aaaffd343d Mon Sep 17 00:00:00 2001 From: mcquenji Date: Sun, 17 Aug 2025 03:11:13 +0200 Subject: [PATCH 20/23] feat!: enhance Struct with toDart/fromDart functions - DateTime lib is still incomplete BREAKING: Structs are now generic and require a toDart and fromDart function upon creation (does not apply to shallow structs) --- bin/dscript.dart | 2 +- lib/src/contract_builder.dart | 32 ++++- lib/src/stdlib/date_time.dart | 35 ++++++ lib/src/stdlib/http.dart | 29 ++--- lib/src/stdlib/json.dart | 8 +- lib/src/stdlib/map.dart | 6 +- lib/src/types.dart | 220 ++++++++++++++++++++++++++++------ pubspec.lock | 6 +- test/shared.dart | 29 ++++- 9 files changed, 284 insertions(+), 83 deletions(-) create mode 100644 lib/src/stdlib/date_time.dart diff --git a/bin/dscript.dart b/bin/dscript.dart index a8e07df..fb30c81 100644 --- a/bin/dscript.dart +++ b/bin/dscript.dart @@ -47,7 +47,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.', diff --git a/lib/src/contract_builder.dart b/lib/src/contract_builder.dart index deb9d62..ace90a0 100644 --- a/lib/src/contract_builder.dart +++ b/lib/src/contract_builder.dart @@ -265,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); @@ -289,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() { @@ -302,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!, ); } } @@ -315,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/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/http.dart b/lib/src/stdlib/http.dart index 848bedf..8ae1188 100644 --- a/lib/src/stdlib/http.dart +++ b/lib/src/stdlib/http.dart @@ -27,18 +27,6 @@ class HttpBindings extends LibraryBinding { optionsBinding, }; - /// Maps a [Dio] [Response] to it's dsl type representation. - static Map mapResponse(Response response) { - return { - $Type.structKey: Struct.httpResponse.name, - 'statusCode': response.statusCode, - 'data': response.data.toString(), - 'headers': response.headers.map, - 'isRedirect': response.isRedirect, - 'statusMessage': response.statusMessage, - }; - } - /// Binding for making a GET request. static final getBinding = RuntimeBinding( name: 'get', @@ -54,7 +42,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -94,7 +82,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -135,7 +123,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -176,7 +164,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -217,7 +205,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -256,7 +244,7 @@ class HttpBindings extends LibraryBinding { headers: headers, ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -295,7 +283,7 @@ class HttpBindings extends LibraryBinding { method: 'OPTIONS', // Set the method to OPTIONS ), ); - return mapResponse(response); + return Struct.httpResponse.fromDart(response); }, positionalParams: { 'path': PrimitiveType.STRING, @@ -318,6 +306,3 @@ class HttpBindings extends LibraryBinding { 'Makes an OPTIONS request to the specified [path] with optional query parameters and headers.', ); } - -/// Typedef for the [Struct.httpResponse] type. -typedef HttpResponse = Map; diff --git a/lib/src/stdlib/json.dart b/lib/src/stdlib/json.dart index e109894..c745e8e 100644 --- a/lib/src/stdlib/json.dart +++ b/lib/src/stdlib/json.dart @@ -25,13 +25,7 @@ class JsonBindings extends LibraryBinding { function: (String str) { final json = jsonDecode(str); - return { - 'map': json is Map ? json : null, - 'list': json is List ? json : null, - 'isMap': json is Map, - 'isList': json is List, - $Type.structKey: Struct.json.name, - }; + return Struct.json.fromDart(json); }, returnType: Struct.json, positionalParams: {'str': PrimitiveType.STRING}, diff --git a/lib/src/stdlib/map.dart b/lib/src/stdlib/map.dart index 11fcb52..f4268e7 100644 --- a/lib/src/stdlib/map.dart +++ b/lib/src/stdlib/map.dart @@ -224,11 +224,7 @@ class MapBindings extends LibraryBinding { static final entriesBinding = RuntimeBinding( name: 'entries', function: (Map map) => map.entries.map((entry) { - return { - 'key': entry.key, - 'value': entry.value, - $Type.structKey: Struct.mapEntry.name, - }; + return Struct.mapEntry.fromDart(entry); }).toList(), positionalParams: { 'map': MapType( diff --git a/lib/src/types.dart b/lib/src/types.dart index ec94d11..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'; @@ -198,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); } } } @@ -207,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; @@ -256,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. /// @@ -594,22 +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); + : this( + name: name, + fields: const {}, + nullable: false, + toDart: _shallowToDart, + fromDart: _shallowFromDart, + ); @override Map toMap() { @@ -629,6 +691,8 @@ class Struct extends $Type { name: name, fields: fields, nullable: nullable, + toDart: _toDart, + fromDart: _fromDart, ); } @@ -655,59 +719,102 @@ 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( + static final httpResponse = Struct( name: 'HttpResponse', fields: { 'statusCode': PrimitiveType.INT, 'headers': MapType( keyType: PrimitiveType.STRING, - valueType: PrimitiveType.STRING, + valueType: ListType(elementType: PrimitiveType.STRING), ), - 'body': PrimitiveType.STRING.asNullable(), + '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, }, - nullable: false, 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, - }, - ); + 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 = const Struct( + static final mapEntry = Struct( name: 'MapEntry', description: 'Represents a key-value pair in a map.', fields: { - 'key': DynamicType(), - 'value': DynamicType(), + '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 = const Struct( + static final dateTime = Struct( name: 'DateTime', fields: { 'year': PrimitiveType.INT, @@ -716,23 +823,64 @@ class Struct extends $Type { '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 [TimeOfDay]. - static final timeOfDay = const Struct( - name: 'TimeOfDay', + /// Represents [Duration]. + static final duration = Struct( + name: 'Duration', fields: { - 'hour': PrimitiveType.INT, - 'minute': PrimitiveType.INT, - 'second': PrimitiveType.INT, + '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), }, - description: 'Represents a time of day.', ); /// Default structs defined within the language. - static final defaults = [error, httpResponse, json]; + static final defaults = [error, httpResponse, json, duration, dateTime]; } /// Signature of a contract. diff --git a/pubspec.lock b/pubspec.lock index 4aed3a2..913cce6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" ci: dependency: transitive description: @@ -585,4 +585,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.2 <4.0.0" + dart: ">=3.8.0 <4.0.0" 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(), ); From c8f391451a8c38ace2e4cbe9cdd775f724a74950 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Sun, 17 Aug 2025 03:36:47 +0200 Subject: [PATCH 21/23] ci: fix failing unit test and update noteKeywords in .versionrc --- .versionrc | 11 ++++++++++- test/analyzer_test.dart | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) 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/test/analyzer_test.dart b/test/analyzer_test.dart index 0659c5a..3a9f9fd 100644 --- a/test/analyzer_test.dart +++ b/test/analyzer_test.dart @@ -59,6 +59,7 @@ void main() { // `external::custom` permission, but the script omits it. final bindContract = contract('BindTest') .bind('testBind', (int x) => x * 2) + .returns(PrimitiveType.DOUBLE) .param('x', PrimitiveType.INT) .permission('custom') .end() From 170529681d5530c10ec8181f141ddb2b7f330072 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Thu, 21 Aug 2025 19:16:57 +0200 Subject: [PATCH 22/23] chore: switch to source gen for stdlib (WIP) --- .fvmrc | 2 +- .gitignore | 1 + .vscode/settings.json | 2 +- bin/dscript.dart | 22 +- docs/language/standard-library.md | 202 +++++++++++++---- lib/src/bindings.dart | 7 +- lib/src/contract_builder.dart | 4 +- lib/src/permissions.dart | 3 +- lib/src/stdlib/base64.dart | 52 ++--- lib/src/stdlib/base64.g.dart | 106 +++++++++ lib/src/stdlib/log.dart | 173 +++++---------- lib/src/stdlib/log.g.dart | 350 ++++++++++++++++++++++++++++++ lib/src/stdlib/stdlib.dart | 5 +- pubspec.lock | 114 +++++++++- pubspec.yaml | 3 + 15 files changed, 845 insertions(+), 201 deletions(-) create mode 100644 lib/src/stdlib/base64.g.dart create mode 100644 lib/src/stdlib/log.g.dart 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/.gitignore b/.gitignore index bc0376d..f5c587a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ *.interp *.tokens coverage/ +custom_lint.log diff --git a/.vscode/settings.json b/.vscode/settings.json index 1cc6922..59dee9f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.29.3", + "dart.flutterSdkPath": ".fvm/versions/3.32.8", "conventionalCommits.scopes": [ "analyzer", "compiler", diff --git a/bin/dscript.dart b/bin/dscript.dart index fb30c81..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) { @@ -59,15 +70,24 @@ void main(List arguments) async { ) .end() .bind('double', (int x) => x * 2) + .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(), ], diff --git a/docs/language/standard-library.md b/docs/language/standard-library.md index bac802b..3e7dc81 100644 --- a/docs/language/standard-library.md +++ b/docs/language/standard-library.md @@ -10,6 +10,7 @@ - [http](#http) - [json](#json) - [utf8](#utf8) +- [base64](#base64) - [log](#log) --- @@ -47,8 +48,10 @@ Represents an HTTP response with status code, headers, and body. | Field | Type | | --- | --- | | statusCode | `int` | -| headers | `Map` | -| body | `string?` | +| headers | `Map>` | +| data | `string?` | +| statusMessage | `string?` | +| isRedirect | `bool` | ### JSON @@ -61,6 +64,34 @@ Result of json::decode. It's either a MapRemoves the first occurrence of element from the list. -### removeAt → `dynamic` +### removeAt → `dynamic?` | Name | Type | Kind | | --- | --- | --- | @@ -753,7 +784,7 @@ Library for working with lists. Removes and returns the element at index from the list. -### removeLast → `dynamic` +### removeLast → `dynamic?` | Name | Type | Kind | | --- | --- | --- | @@ -816,6 +847,15 @@ Library for working with lists. Returns true if the list contains element. +### copy → `List` + +| Name | Type | Kind | +| --- | --- | --- | +| list | `List` | Positional (1) | + +Returns a copy of the list. + + --- @@ -871,7 +911,7 @@ Library for working with maps. Returns true if the map contains the specified value. -### keys → `List` +### keys → `List` | Name | Type | Kind | | --- | --- | --- | @@ -880,7 +920,7 @@ Library for working with maps. Returns a list of all keys in the map. -### values → `List` +### values → `List` | Name | Type | Kind | | --- | --- | --- | @@ -908,7 +948,7 @@ Library for working with maps. Removes all key-value pairs from the map. -### remove → `dynamic` +### remove → `dynamic?` | Name | Type | Kind | | --- | --- | --- | @@ -918,14 +958,42 @@ Library for working with maps. Removes the key-value pair for the specified key from the map. -### keyOf → `dynamic` +### keyOf → `dynamic?` | Name | Type | Kind | | --- | --- | --- | | map | `Map` | Positional (1) | | value | `dynamic?` | Positional (2) | -Returns the key associated with the specified value. +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. @@ -1167,11 +1235,37 @@ Provides UTF-8 encoding and decoding functions. +--- + +## 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 -Logging utilities. +Provides basic logging utitlities. !> Log messages will not be visible when using IsolateRuntime. @@ -1179,70 +1273,98 @@ Logging utilities. | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | -Logs an info message. +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 | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | -Logs a warning message. +Logs potential problems that are not yet errors but might require attention or could lead to issues. -### error → `void` +### severe → `void` | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | - -Logs an error message. +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs serious failures or errors that will likely +prevent normal program execution. +
+
-### debug → `void` +### fine → `void` | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | - -Logs a debug message. +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs tracing information for debugging purposes. +Less verbose than finer or finest. +
+
-### verbose → `void` +### finer → `void` | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | - -Logs a verbose message. +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs fairly detailed tracing information. +Useful when debugging complex flows with more granularity than fine. +
+
-### fatal → `void` +### finest → `void` | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | - -Logs a fatal message. +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs highly detailed tracing information. +Intended for deep debugging, usually too verbose for normal use. +
+
-### critical → `void` +### config → `void` | Name | Type | Kind | | --- | --- | --- | -| message | `dynamic?` | Positional (1) | -| error | `dynamic?` | Named | +| message | `dynamic` | Positional (1) | +| error | `dynamic` | Named | +
+Logs static configuration messages. +Typically used to record startup settings or environment details. +
+
+ + +### shout → `void` -Logs a critical message. +| 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. +
+
diff --git a/lib/src/bindings.dart b/lib/src/bindings.dart index 4df4543..d451992 100644 --- a/lib/src/bindings.dart +++ b/lib/src/bindings.dart @@ -58,7 +58,12 @@ class RuntimeBinding { 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. /// diff --git a/lib/src/contract_builder.dart b/lib/src/contract_builder.dart index ace90a0..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; } diff --git a/lib/src/permissions.dart b/lib/src/permissions.dart index d8c2d13..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`). diff --git a/lib/src/stdlib/base64.dart b/lib/src/stdlib/base64.dart index 46424fd..219e579 100644 --- a/lib/src/stdlib/base64.dart +++ b/lib/src/stdlib/base64.dart @@ -1,39 +1,25 @@ -part of 'stdlib.dart'; +import 'dart:convert'; -/// [base64Encode] and [base64Decode] bindings. -class Base64Bindings extends LibraryBinding { - /// Base64 encoding and decoding functions. - const Base64Bindings() - : super( - name: 'base64', - description: 'Base64 encoding and decoding functions.', - ); +import 'package:dscript_annotations/dscript_annotations.dart'; +import 'package:dscript_dart/dscript_dart.dart'; - /// [base64Encode] binding. - static final encodeBinding = RuntimeBinding( - name: 'encode', - description: '''Encodes the given [str] to a Base64 string.''', - function: (String str) { - return base64Encode(utf8.encode(str)); - }, - positionalParams: {'str': PrimitiveType.STRING}, - returnType: PrimitiveType.STRING, - ); +part 'base64.g.dart'; - /// [base64Decode] binding. - static final decodeBinding = RuntimeBinding( - name: 'decode', - description: '''Decodes the given Base64 [str] to a string.''', - function: (String str) { - return utf8.decode(base64Decode(str)); - }, - positionalParams: {'str': PrimitiveType.STRING}, - returnType: PrimitiveType.STRING, - ); +/// 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 - Set get bindings => { - encodeBinding, - decodeBinding, - }; + 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..9c0da9c --- /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: $Type.from('String'), + positionalParams: {'input': $Type.from('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: $Type.from('String'), + positionalParams: {'input': $Type.from('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/log.dart b/lib/src/stdlib/log.dart index 21cfea7..61b5dc0 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -1,135 +1,78 @@ // coverage:ignore-file -part of 'stdlib.dart'; -/// Bindings for the logging standard library. -class LogBindings extends LibraryBinding { +// ignore_for_file: unnecessary_question_mark necessary for the source gen to pick it up + +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', - description: - 'Logging utilities.\n\n!> Log messages will not be visible when using [IsolateRuntime].', - ) { + 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs an info message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a warning message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs an error message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a debug message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a verbose message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a fatal message.', - returnType: PrimitiveType.VOID, - ); + /// 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: { - 'message': const DynamicType(), - }, - namedParams: { - #error: const DynamicType(), - }, - description: 'Logs a critical message.', - returnType: PrimitiveType.VOID, - ); + /// 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..35fc311 --- /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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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': $Type.from('dynamic')}, + namedParams: {#error: $Type.from('dynamic')}, + 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/stdlib.dart b/lib/src/stdlib/stdlib.dart index c7fa762..7f34c17 100644 --- a/lib/src/stdlib/stdlib.dart +++ b/lib/src/stdlib/stdlib.dart @@ -5,12 +5,12 @@ 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'; @@ -18,7 +18,6 @@ part 'dynamic.dart'; part 'http.dart'; part 'json.dart'; part 'utf8.dart'; -part 'base64.dart'; /// A library binding that contains a list of runtime bindings. /// This class is used to group related bindings together, such as math diff --git a/pubspec.lock b/pubspec.lock index 913cce6..b2d920e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,70 @@ 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: @@ -89,6 +153,14 @@ packages: 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: @@ -184,6 +256,20 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + dscript_annotations: + dependency: "direct main" + description: + path: "../dscript_annotations" + relative: true + source: path + version: "1.0.0" + dscript_gen: + dependency: "direct dev" + description: + path: "../dscript_gen" + relative: true + source: path + version: "1.0.0" equatable: dependency: "direct main" description: @@ -232,6 +318,14 @@ 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: @@ -305,7 +399,7 @@ packages: source: hosted version: "0.12.17" meta: - dependency: "direct main" + dependency: transitive description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" @@ -416,6 +510,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: @@ -512,6 +614,14 @@ packages: 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: "1.0.2" typed_data: dependency: transitive description: @@ -585,4 +695,4 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" + dart: ">=3.8.1 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0ff1a33..dcd86ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ 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 @@ -17,8 +18,10 @@ dependencies: result_dart: ^2.1.0 dev_dependencies: + build_runner: ^2.4.11 custom_lint: ^0.8.0 custom_lints: path: ./custom_lints + dscript_gen: ^1.0.0 lints: ^5.0.0 test: ^1.24.0 From fe4a0b6d0b544ea660e1e84cb3601126497161c0 Mon Sep 17 00:00:00 2001 From: mcquenji Date: Sat, 23 Aug 2025 03:51:41 +0200 Subject: [PATCH 23/23] chore: use new sourcegen version --- lib/src/stdlib/base64.g.dart | 8 ++++---- lib/src/stdlib/log.dart | 18 ++++++++---------- lib/src/stdlib/log.g.dart | 32 ++++++++++++++++---------------- pubspec.lock | 18 ++++++++++-------- pubspec.yaml | 5 ++++- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/lib/src/stdlib/base64.g.dart b/lib/src/stdlib/base64.g.dart index 9c0da9c..6328e86 100644 --- a/lib/src/stdlib/base64.g.dart +++ b/lib/src/stdlib/base64.g.dart @@ -36,8 +36,8 @@ abstract class _$Base64Bindings extends LibraryBinding { name: 'encode', description: 'Encodes [input] to Base64 format.', function: encode, - returnType: $Type.from('String'), - positionalParams: {'input': $Type.from('String')}, + returnType: PrimitiveType.STRING, + positionalParams: {'input': PrimitiveType.STRING}, namedParams: {}, permissions: const [], preMiddlewares: _encodePreMiddlewares, @@ -64,8 +64,8 @@ abstract class _$Base64Bindings extends LibraryBinding { name: 'decode', description: 'Decodes [input] from Base64 format.', function: decode, - returnType: $Type.from('String'), - positionalParams: {'input': $Type.from('String')}, + returnType: PrimitiveType.STRING, + positionalParams: {'input': PrimitiveType.STRING}, namedParams: {}, permissions: const [], preMiddlewares: _decodePreMiddlewares, diff --git a/lib/src/stdlib/log.dart b/lib/src/stdlib/log.dart index 61b5dc0..7dda138 100644 --- a/lib/src/stdlib/log.dart +++ b/lib/src/stdlib/log.dart @@ -1,7 +1,5 @@ // coverage:ignore-file -// ignore_for_file: unnecessary_question_mark necessary for the source gen to pick it up - import 'package:dscript_annotations/dscript_annotations.dart'; import 'package:logging/logging.dart'; import 'package:dscript_dart/dscript_dart.dart'; @@ -25,54 +23,54 @@ class LogBindings extends _$LogBindings { /// Logs general informational messages that highlight the progress of the application at a coarse level. @override - void info(dynamic message, {dynamic? error}) { + void info(dynamic message, {dynamic error}) { logger.info(message, error); } /// Logs potential problems that are not yet errors but might require attention or could lead to issues. @override - void warning(dynamic message, {dynamic? error}) { + void warning(dynamic message, {dynamic error}) { logger.warning(message, error); } /// Logs serious failures or errors that will likely prevent normal program execution. @override - void severe(dynamic message, {dynamic? error}) { + void severe(dynamic message, {dynamic error}) { logger.severe(message, error); } /// Logs tracing information for debugging purposes. /// Less verbose than [finer] or [finest]. @override - void fine(dynamic message, {dynamic? error}) { + void fine(dynamic message, {dynamic error}) { logger.fine(message, error); } /// Logs fairly detailed tracing information. /// Useful when debugging complex flows with more granularity than [fine]. @override - void finer(dynamic message, {dynamic? error}) { + void finer(dynamic message, {dynamic error}) { logger.finer(message, error); } /// Logs highly detailed tracing information. /// Intended for deep debugging, usually too verbose for normal use. @override - void finest(dynamic message, {dynamic? error}) { + void finest(dynamic message, {dynamic error}) { logger.finest(message, error); } /// Logs static configuration messages. /// Typically used to record startup settings or environment details. @override - void config(dynamic message, {dynamic? error}) { + void config(dynamic message, {dynamic error}) { logger.config(message, error); } /// Logs messages at the shout level. /// Louder than [severe]; use sparingly for attention-grabbing events. @override - void shout(dynamic message, {dynamic? error}) { + 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 index 35fc311..56504d6 100644 --- a/lib/src/stdlib/log.g.dart +++ b/lib/src/stdlib/log.g.dart @@ -47,8 +47,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs general informational messages that highlight the progress of the application at a coarse level.', function: info, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _infoPreMiddlewares, postMiddlewares: _infoPostMiddlewares, @@ -76,8 +76,8 @@ abstract class _$LogBindings extends LibraryBinding { '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': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _warningPreMiddlewares, postMiddlewares: _warningPostMiddlewares, @@ -105,8 +105,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs serious failures or errors that will likely prevent normal program execution.', function: severe, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _severePreMiddlewares, postMiddlewares: _severePostMiddlewares, @@ -135,8 +135,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs tracing information for debugging purposes.\nLess verbose than [finer] or [finest].', function: fine, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _finePreMiddlewares, postMiddlewares: _finePostMiddlewares, @@ -165,8 +165,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs fairly detailed tracing information.\nUseful when debugging complex flows with more granularity than [fine].', function: finer, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _finerPreMiddlewares, postMiddlewares: _finerPostMiddlewares, @@ -195,8 +195,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs highly detailed tracing information.\nIntended for deep debugging, usually too verbose for normal use.', function: finest, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _finestPreMiddlewares, postMiddlewares: _finestPostMiddlewares, @@ -225,8 +225,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs static configuration messages.\nTypically used to record startup settings or environment details.', function: config, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _configPreMiddlewares, postMiddlewares: _configPostMiddlewares, @@ -255,8 +255,8 @@ abstract class _$LogBindings extends LibraryBinding { 'Logs messages at the shout level.\nLouder than [severe]; use sparingly for attention-grabbing events.', function: shout, returnType: $Type.from('void'), - positionalParams: {'message': $Type.from('dynamic')}, - namedParams: {#error: $Type.from('dynamic')}, + positionalParams: {'message': const DynamicType()}, + namedParams: {#error: const DynamicType()}, permissions: const [], preMiddlewares: _shoutPreMiddlewares, postMiddlewares: _shoutPostMiddlewares, diff --git a/pubspec.lock b/pubspec.lock index b2d920e..c4d84fb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -259,17 +259,19 @@ packages: dscript_annotations: dependency: "direct main" description: - path: "../dscript_annotations" - relative: true - source: path + name: dscript_annotations + sha256: b75cbdfa1d4fdac5a300e40beb77ff1f0b658ba2c4a0309dde4db5418a90781e + url: "https://pub.dev" + source: hosted version: "1.0.0" dscript_gen: dependency: "direct dev" description: - path: "../dscript_gen" - relative: true - source: path - version: "1.0.0" + name: dscript_gen + sha256: "3dbbc5581bffd1749e6a5ea782d4e65364edc561ed79b21a29fc02be7ea4f2c9" + url: "https://pub.dev" + source: hosted + version: "1.1.1" equatable: dependency: "direct main" description: @@ -399,7 +401,7 @@ packages: source: hosted version: "0.12.17" meta: - dependency: transitive + dependency: "direct main" description: name: meta sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" diff --git a/pubspec.yaml b/pubspec.yaml index dcd86ab..86c39f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,9 @@ dev_dependencies: custom_lint: ^0.8.0 custom_lints: path: ./custom_lints - dscript_gen: ^1.0.0 + dscript_gen: ^1.1.1 lints: ^5.0.0 test: ^1.24.0 +# dependency_overrides: +# dscript_gen: +# path: ../dscript_gen