From 11da3ddced4fd12d0dba12487261d5f838586e50 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 3 Nov 2025 17:46:52 +0100 Subject: [PATCH] simple textmate syntax highlighting --- Cargo.lock | 1 + Cargo.toml | 1 + examples/api/customer/controller.crud | 23 +++++ .../api/{customers.dao => customer/db.crud} | 0 .../{customers.svc => customer/service.crud} | 0 examples/api/customers.ctl | 6 -- examples/model/customers | 2 +- src/main.rs | 80 ++++++++++++---- src/scanner.rs | 1 + syntax/crud/.vscodeignore | 4 + syntax/crud/CHANGELOG.md | 9 ++ syntax/crud/language-configuration.json | 25 +++++ syntax/crud/package.json | 32 +++++++ syntax/crud/syntaxes/crud.tmLanguage.json | 95 +++++++++++++++++++ syntax/crud/vsc-extension-quickstart.md | 29 ++++++ 15 files changed, 280 insertions(+), 28 deletions(-) create mode 100644 examples/api/customer/controller.crud rename examples/api/{customers.dao => customer/db.crud} (100%) rename examples/api/{customers.svc => customer/service.crud} (100%) delete mode 100644 examples/api/customers.ctl create mode 100644 syntax/crud/.vscodeignore create mode 100644 syntax/crud/CHANGELOG.md create mode 100644 syntax/crud/language-configuration.json create mode 100644 syntax/crud/package.json create mode 100644 syntax/crud/syntaxes/crud.tmLanguage.json create mode 100644 syntax/crud/vsc-extension-quickstart.md diff --git a/Cargo.lock b/Cargo.lock index 0f3f76e..358fa27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,7 @@ dependencies = [ "tower-livereload", "tracing", "tracing-subscriber", + "url", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index 7a6534a..5d751e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ tower-livereload = "0.9.6" log = "0.4.28" walkdir = "2.5.0" thiserror = "2.0.17" +url = "2.5.7" diff --git a/examples/api/customer/controller.crud b/examples/api/customer/controller.crud new file mode 100644 index 0000000..21b2fa8 --- /dev/null +++ b/examples/api/customer/controller.crud @@ -0,0 +1,23 @@ +#path(/id/{uuid}) +fn get(uuid: uuid) -> Customer: + service.get(uuid)?or(404) + +#path(?firstname={name}) +fn get(name: string) -> Customer: + if name: + service.get(id) + else: + 404 + +#path(/) +fn get() -> list: + service.get_all() + +#path(/) +fn post(customer: Customer): + service.add(customer) + + +#path() +fn put(customer: Customer): + service.update(customer) diff --git a/examples/api/customers.dao b/examples/api/customer/db.crud similarity index 100% rename from examples/api/customers.dao rename to examples/api/customer/db.crud diff --git a/examples/api/customers.svc b/examples/api/customer/service.crud similarity index 100% rename from examples/api/customers.svc rename to examples/api/customer/service.crud diff --git a/examples/api/customers.ctl b/examples/api/customers.ctl deleted file mode 100644 index 60719d9..0000000 --- a/examples/api/customers.ctl +++ /dev/null @@ -1,6 +0,0 @@ -///gets the customer -fn get(id: u32) -> Customer: - service.get(id) - -fn post(customer: Customer): - service.add(customer) diff --git a/examples/model/customers b/examples/model/customers index ea98d7c..199b224 100644 --- a/examples/model/customers +++ b/examples/model/customers @@ -1,4 +1,4 @@ -struct Customer: +object Customer: id: u32, first_name: string, last_name: string, diff --git a/src/main.rs b/src/main.rs index 9056623..3f43633 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,16 @@ -use axum::extract::{State}; +use axum::extract::{Request, State}; use axum::http::StatusCode; -use axum::routing::get; +use axum::routing::{any, get}; use axum::{Json, Router}; use crudlang::ast_compiler; use crudlang::bytecode_compiler::compile; use crudlang::chunk::Chunk; use crudlang::scanner::scan; -use crudlang::vm::{interpret, interpret_async}; +use crudlang::vm::{interpret_async}; use std::collections::HashMap; use std::fs; use std::sync::Arc; +use axum::response::IntoResponse; use walkdir::WalkDir; use crudlang::errors::Error; use crudlang::errors::Error::Platform; @@ -46,28 +47,40 @@ async fn main() -> Result<(), crudlang::errors::Error> { } let registry = Arc::new(registry); - if !paths.is_empty() { - let mut app = Router::new(); - for (path, code) in paths.iter() { - let state = Arc::new(AppState { - name: format!("{}.get", path), - registry: registry.clone(), - }); - println!("adding {}", path); - app = app.route( - &format!("/{}", path.replace("/web", "")), - get(handle_get).with_state(state.clone()), - ); - } - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.map_err(map_underlying())?; - println!("listening on {}", listener.local_addr().map_err(map_underlying())?); - axum::serve(listener, app).await.map_err(map_underlying())?; - } + // if !paths.is_empty() { + // let mut app = Router::new(); + // for (path, code) in paths.iter() { + // let state = Arc::new(AppState { + // name: format!("{}.get", path), + // registry: registry.clone(), + // }); + // println!("adding {}", path); + // app = app.route( + // &format!("/{}", path.replace("/web", "")), + // get(handle_get).with_state(state.clone()), + // ); + // } + // let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.map_err(map_underlying())?; + // println!("listening on {}", listener.local_addr().map_err(map_underlying())?); + // axum::serve(listener, app).await.map_err(map_underlying())?; + // } + + let app = Router::new() + .route("/", any(handle_any)) + .route("/{*path}", any(handle_any)); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") + .await + .map_err(map_underlying())?; + + println!("listening on {}", listener.local_addr().map_err(map_underlying())?); + + axum::serve(listener, app).await.map_err(map_underlying())?; Ok(()) } fn map_underlying() -> fn(std::io::Error) -> Error { - |e| Error::Platform(e.to_string()) + |e| Platform(e.to_string()) } #[derive(Clone)] @@ -85,3 +98,28 @@ async fn handle_get(State(state): State>) -> Result, )) } + +async fn handle_any(req: Request) -> impl IntoResponse { + let method = req.method().clone(); + let uri = req.uri(); + + // Parse path segments + let path_segments: Vec<&str> = uri.path() + .split('/') + .filter(|s| !s.is_empty()) + .collect(); + + // Parse query parameters + let query_params: HashMap = uri.query() + .map(|q| { + url::form_urlencoded::parse(q.as_bytes()) + .into_owned() + .collect() + }) + .unwrap_or_default(); + + format!( + "Method: {}\nPath: {}\nSegments: {:?}\nQuery: {:?}", + method, uri.path(), path_segments, query_params + ) +} diff --git a/src/scanner.rs b/src/scanner.rs index 7ce18ce..e2b0c05 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -58,6 +58,7 @@ impl Scanner { }; self.add_token(t); } + '#' => self.add_token(TokenType::Hash), '+' => self.add_token(TokenType::Plus), ':' => self.add_token(TokenType::Colon), ';' => println!("Warning: Ignoring semicolon at line {}", self.line), diff --git a/syntax/crud/.vscodeignore b/syntax/crud/.vscodeignore new file mode 100644 index 0000000..f369b5e --- /dev/null +++ b/syntax/crud/.vscodeignore @@ -0,0 +1,4 @@ +.vscode/** +.vscode-test/** +.gitignore +vsc-extension-quickstart.md diff --git a/syntax/crud/CHANGELOG.md b/syntax/crud/CHANGELOG.md new file mode 100644 index 0000000..3cb92d6 --- /dev/null +++ b/syntax/crud/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "crud" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/syntax/crud/language-configuration.json b/syntax/crud/language-configuration.json new file mode 100644 index 0000000..d43e282 --- /dev/null +++ b/syntax/crud/language-configuration.json @@ -0,0 +1,25 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ "/*", "*/" ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} \ No newline at end of file diff --git a/syntax/crud/package.json b/syntax/crud/package.json new file mode 100644 index 0000000..d61d979 --- /dev/null +++ b/syntax/crud/package.json @@ -0,0 +1,32 @@ +{ + "name": "crud", + "displayName": "crud", + "description": "crud-lang", + "version": "0.0.1", + "engines": { + "vscode": "^1.105.0" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [{ + "id": "crud", + "aliases": ["crud-lang", "crud"], + "extensions": [".crud"], + "configuration": "./language-configuration.json" + }], + "grammars": [{ + "language": "crud", + "scopeName": "source.crud", + "path": "./syntaxes/crud.tmLanguage.json" + }], + "semanticTokenScopes": [ + { + "scopes": { + "crud.custom.scope": ["annotation.crud"] + } + } + ] + } +} diff --git a/syntax/crud/syntaxes/crud.tmLanguage.json b/syntax/crud/syntaxes/crud.tmLanguage.json new file mode 100644 index 0000000..1ba3554 --- /dev/null +++ b/syntax/crud/syntaxes/crud.tmLanguage.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "crud-lang", + "patterns": [ + { + "include": "#keywords" + }, + { + "include": "#operators" + }, + { + "include": "#strings" + }, + { + "include": "#chars" + } + ], + "repository": { + "keywords": { + "patterns": [ + { + "name": "variable.other.crud", + "match": "(#.+?\\(.*?\\))" + }, + { + "name": "keyword.control.crud", + "match": "\\b(fn)\\b" + }, + { + "name": "storage.type.crud", + "match": "\\b(u32|u64|i32|i64\f32|f64|string|date|char|list|map|bool)\\b" + }, + { + "name": "support.function.crud", + "match": "\\b(get|put|post|delete|patch|options)\\b" + }, + { + "name": "constant.numeric.crud", + "match": "\\b[0-9]+\\.?[0-9]*\\b" + }, + { + "name": "constant.language.crud", + "match": "\\b(true|false)\\b" + }, + { + "name": "constant.character.escape.crud", + "match": "\\\\[nrt\\\\'\"]" + }, + { + "name": "comment.line.crud", + "match": "(//.*)" + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.arithmetic.crud", + "match": "\\+|\\-|\\*|\\/" + }, + { + "name": "keyword.operator.comparison.crud", + "match": "==|!=|<=|>=|<|>" + }, + { + "name": "keyword.operator.assignment.crud", + "match": "=" + } + ] + }, + "strings": { + "name": "string.quoted.double.crud", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.crud", + "match": "\\\\." + } + ] + }, + "chars": { + "name": "string.quoted.single.crud", + "begin": "'", + "end": "'", + "patterns": [ + { + "name": "constant.character.escape.crud", + "match": "\\\\." + } + ] + } + }, + "scopeName": "source.crud" +} \ No newline at end of file diff --git a/syntax/crud/vsc-extension-quickstart.md b/syntax/crud/vsc-extension-quickstart.md new file mode 100644 index 0000000..c3ce634 --- /dev/null +++ b/syntax/crud/vsc-extension-quickstart.md @@ -0,0 +1,29 @@ +# Welcome to your VS Code Extension + +## What's in the folder + +* This folder contains all of the files necessary for your extension. +* `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension. +* `syntaxes/crud.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization. +* `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets. + +## Get up and running straight away + +* Make sure the language configuration settings in `language-configuration.json` are accurate. +* Press `F5` to open a new window with your extension loaded. +* Create a new file with a file name suffix matching your language. +* Verify that syntax highlighting works and that the language configuration settings are working. + +## Make changes + +* You can relaunch the extension from the debug toolbar after making changes to the files listed above. +* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. + +## Add more language features + +* To add features such as IntelliSense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/api/language-extensions/overview + +## Install your extension + +* To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code. +* To share your extension with the world, read on https://code.visualstudio.com/api/working-with-extensions/publishing-extension about publishing an extension.