Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .github/workflows/dmlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Dream Maker structural lint

on:
pull_request:
paths:
- "code/**/*.dm"
- "tools/dmlint/**"
- ".github/workflows/dmlint.yml"
push:
branches:
- master
paths:
- "code/**/*.dm"
- "tools/dmlint/**"
- ".github/workflows/dmlint.yml"

permissions:
contents: read

jobs:
dmlint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Run unit tests
working-directory: tools/dmlint
run: python -m unittest -v
- name: Lint Dream Maker source
run: python tools/dmlint/dmlint.py
71 changes: 71 additions & 0 deletions tools/dmlint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# dmlint

`dmlint` is a standalone, dependency-free structural linter for Dream Maker
source. It is intended for fast local feedback and CI checks without launching
or embedding DreamMaker.

It recursively indexes `code/**/*.dm`, tracks exact source locations, ignores
syntax-like characters inside comments and strings, and validates:

- terminated string literals and block comments;
- paired parentheses, brackets, and braces;
- balanced `#if`/`#ifdef`/`#ifndef` and `#endif` directives;
- placement of `#else` and `#elif`;
- required `#define` and `#include` arguments;
- trailing whitespace.

This tool deliberately does not claim compiler equivalence. Dream Maker has
context-sensitive language behavior that a small standalone checker cannot
reproduce safely. Rules here are conservative and modular so valid project
syntax is not rejected merely because the linter does not understand it.

## Usage

From the repository root:

```sh
python3 tools/dmlint/dmlint.py
python3 tools/dmlint/dmlint.py --format json
python3 tools/dmlint/dmlint.py --warnings-as-errors
```

The optional positional argument selects another repository root. Text output
uses the stable form:

```text
code/example.dm:12:8: error DM003: unexpected closing delimiter ']'
```

The exit status is `1` when an error is found, `2` for invocation or repository
layout errors, and `0` otherwise. `--warnings-as-errors` also returns `1` for
warning-only output.

## Tests

```sh
cd tools/dmlint
python3 -m unittest -v
```

The tests cover comment/string handling, exact locations, delimiter and
preprocessor validation, recursive discovery, machine-readable output, and
exit-code behavior.

## Architecture and extension

`tokenize` performs one linear pass and emits only syntax-significant tokens,
making repository-scale cost proportional to bytes read. Validation functions
consume that token stream independently and return immutable `Diagnostic`
objects. `line_diagnostics` demonstrates a source-line rule that does not need
tokens.

To add a rule:

1. write a pure function accepting tokens or source lines plus the path;
2. return diagnostics with a unique code, precise location, and actionable
message;
3. call it from `lint_text`;
4. add positive and negative tests, especially for comments and strings.

The implementation uses only the Python standard library and never shells out
to DreamMaker or another parser.
Loading