Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ebc4f1e
feat(stdlib): expand standard library
mcquenji Jun 15, 2025
ce70c9e
feat(stdlib): support binding middleware and custom stdlibs
mcquenji Jun 15, 2025
8cea24d
docs(stdlib): add script for generating docs
mcquenji Jun 16, 2025
f3e1d25
fix(compiler): actually handle assignments
mcquenji Jun 16, 2025
ec76354
fix(stdlib): throw when trying to get length of an unsopported type
mcquenji Jun 16, 2025
f6a74bc
docs: update docs
mcquenji Jun 16, 2025
a22a95a
fix(vm): resolve infinite loop when catch block throws
mcquenji Jun 16, 2025
3c6209b
chore(stdlib): remove print statements
mcquenji Jun 16, 2025
8416483
feat(stdlib): implement http bindings
mcquenji Jun 18, 2025
228865f
fix(compiler): make for in loops work
mcquenji Jun 18, 2025
b648a35
docs(stdlib): add documentation for customizing the stdlib
mcquenji Jun 18, 2025
7674bd1
feat(stdlib): implement json & utf8 lib
mcquenji Jun 18, 2025
56a3d68
ci(docs): auto upddate docs
mcquenji Jun 18, 2025
bd7f83e
docs: add motivation to readme
mcquenji Jun 19, 2025
fde3a1a
feat(stdlib): implement base64 bindings
mcquenji Jun 20, 2025
3642ba6
feat(stdlib): add more collection utils
mcquenji Jun 23, 2025
137afee
fix!: refrain from type inference by name
mcquenji Aug 11, 2025
2509d98
ci: prevent reporter from failing and fix fail checks
mcquenji Aug 11, 2025
35bf347
ci: huh
mcquenji Aug 11, 2025
b73e686
feat!: enhance Struct with toDart/fromDart functions
mcquenji Aug 17, 2025
c8f3914
ci: fix failing unit test and update noteKeywords in .versionrc
mcquenji Aug 17, 2025
1705296
chore: switch to source gen for stdlib (WIP)
mcquenji Aug 21, 2025
fe4a0b6
chore: use new sourcegen version
mcquenji Aug 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .fvmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"flutter": "3.29.3"
"flutter": "3.32.8"
}
38 changes: 38 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -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



5 changes: 2 additions & 3 deletions .github/workflows/report.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

name: Report Test Results

on:
workflow_run:
workflows: ["Unit Tests"]
Expand All @@ -22,8 +23,6 @@ jobs:
name: 🧪 Unit Tests
path: test-results.json
reporter: dart-json
fail-on-empty: true
fail-on-error: true
max-annotations: 50


2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
*.interp
*.tokens
coverage/
custom_lint.log
11 changes: 10 additions & 1 deletion .versionrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,14 @@
],
"packageFiles": [
"pubspec.yaml"
]
],
"parserOpts": {
"noteKeywords": [
"BREAKING CHANGE",
"BREAKING CHANGES",
"BREAKING",
"BREAKING-CHANGE",
"BREAKING-CHANGES"
]
}
}
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"dart.flutterSdkPath": ".fvm/versions/3.29.3",
"dart.flutterSdkPath": ".fvm/versions/3.32.8",
"conventionalCommits.scopes": [
"analyzer",
"compiler",
"runtime",
"vm"
"vm",
"stdlib"
],
"conventionalCommits.autoCommit": false,
"conventionalCommits.gitmoji": false,
Expand Down
31 changes: 5 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
162 changes: 162 additions & 0 deletions bin/doc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import 'dart:io';
import 'package:dscript_dart/dscript_dart.dart';

void main() async {
final sb = StringBuffer();

sb.writeln('# Standard Library');
sb.writeln();
// Generate Table of Contents
sb.writeln();
final libraries = LibraryBinding.stdLib();
for (final lib in libraries) {
final anchor = _toAnchor(lib.name);
sb.writeln('- [${lib.name}](#$anchor)');
}
sb.writeln('---');

sb.writeln();
sb.writeln('## Globals');
sb.writeln();
sb.writeln(
'The following globals are available in the dscript runtime. They are not part of any library.',
);
sb.writeln();
sb.writeln();

sb.writeln('| Name | Type | Value |');
sb.writeln('| --- | --- | --- |');

for (final glob in TypeScope.globals.entries) {
sb.writeln('| ${glob.key} | `${glob.value.$2}` | ${glob.value.$1} |');
}

sb.writeln();

sb.writeln('## Structs');

for (final struct in Struct.defaults) {
sb.writeln();
sb.writeln('### ${struct.name}');
sb.writeln();
sb.writeln(struct.description?.docstring);
sb.writeln();

if (struct.fields.isNotEmpty) {
sb.writeln('| Field | Type |');
sb.writeln('| --- | --- |');
for (final field in struct.fields.entries) {
sb.writeln('| ${field.key} | `${field.value}` |');
}
} else {
sb.writeln('This struct has no fields.');
}
}

sb.writeln();
sb.writeln();

// Document each library
for (final lib in libraries) {
sb.writeln();
sb.writeln('---');
sb.writeln();
sb.writeln(_documentLibrary(lib));
}

final output = sb.toString();
final file = File('docs/language/standard-library.md');
await file.writeAsString(output);
}

String _toAnchor(String text) {
return text
.trim()
.toLowerCase()
.replaceAll(RegExp(r'[^a-z0-9\s-]'), '')
.replaceAll(RegExp(r'\s+'), '-');
}

String _documentLibrary(LibraryBinding lib) {
final sb = StringBuffer();

sb.writeln('## ${lib.name}');
sb.writeln();
sb.writeln(lib.description.docstring);
sb.writeln();

for (final binding in lib.bindings) {
sb.writeln(_documentBinding(binding));
sb.writeln();
}

return sb.toString();
}

String _documentBinding(RuntimeBinding binding) {
final sb = StringBuffer();

// Binding Header with anchor
sb.writeln('### ${binding.name} → `${binding.returnType}`');

// Combine positional and named params in one table
final params = <Map<String, dynamic>>[];
for (var i = 0; i < binding.positionalParams.length; i++) {
final p = binding.positionalParams.entries.elementAt(i);
params.add(
{'name': p.key, 'type': p.value, 'kind': 'Positional (${i + 1})'},
);
}
for (final entry in binding.namedParams.entries) {
final name = entry.key
.toString()
.substring('Symbol("'.length, entry.key.toString().length - 2);
params.add({
'name': name,
'kind': 'Named',
'type': entry.value.toString(),
});
}

if (params.isNotEmpty) {
sb.writeln();
sb.writeln('| Name | Type | Kind |');
sb.writeln('| --- | --- | --- |');
for (final p in params) {
sb.writeln('| ${p['name']} | `${p['type']}` | ${p['kind']} |');
}
}

if (binding.description != null && binding.description!.isNotEmpty) {
final description = binding.description!.docstring;

if (description.contains('\n')) {
final summary = description.split('\n').first;
final details = description.substring(summary.length).trim();
sb.writeln('<details>');
sb.writeln('<summary><em>$summary</em></summary>');
sb.writeln('<em>$details</em>');
sb.writeln('</details>');
sb.writeln('<br>');
} else {
sb.writeln();
sb.writeln('<em>$description</em>');
}
}

if (binding.permissions.isNotEmpty) {
sb.writeln();
sb.writeln('#### Permissions');
sb.writeln();
sb.writeln(binding.permissions.map((p) => '`$p`').join(', '));
}

return sb.toString();
}

extension on String {
String get docstring => trim().replaceAllMapped(
RegExp(r'\[([^\]]+)\]'),
(m) => '<code>${m.group(1)}</code>',
);
}
Loading