Skip to content

Référence DSL

Coders génère du code multi-langage et multi-plateforme à partir d'un DSL (.jssp). Les ressources déclarées (controller, mapper, message, error, etc.) sont converties 1:1 via des templates de plateforme.

Pour démarrer, voir Démarrage rapide et Aperçu.

Notes de configuration

config.yml contrôle le comportement global du CLI.

  • entry: fichier .jssp racine
  • projects: platform, name, outPath, entry, target, language, options
  • llmOptions: provider, model, url, apiKey, timeoutSeconds, stream

Vue d’ensemble DSL

Le DSL Coders se compose de :

  • déclarations : domain, table, entity, mapper, struct, class, controller, api, define
  • exécution : func, main, @prompt
  • ressources : message, error, property, css
  • UI : html + template

Modélisation des données

Domain

js
domain Url string(512);
domain Name string(32);

Table

js
table user {
  user_id int64 auto;
  name Name;
  create_at datetime = now();
  key(user_id);
  index(create_at);
  unique index(name);
}

Data

js
data user(name) {
  ('user1');
  ('user2');
}

Entity

js
entity UserVo from @table.user {}

entity UserProfileVo {
  var userId int64;
  var name Name;
  var avatarUrl Url;
}

Mapper

js
mapper UserMapper {
  query insertUser(name Name) int32 {
    insert user(name)
    values(:name);
  }

  query updateUser(userId int64, name Name) int32 {
    update user set name = :name
    where user_id = :userId;
  }
}

Procédures/Fonctions DB

js
dbproc sample(userId string(10), out name string(100)) {
  var _name string(100);
  var c cursor = select name into _name from user_id = userId;
  c.open();
  c.fetch();
  name = _name;
  c.close();
}

dbfunc countUser() int32 {
  var _count int32;
  select count(*) into _count from user;
  return _count;
}

Structure applicative

Struct / Class / Interface

js
struct SignupRequest {
  var email string;
  var password string;
}

abstract class BaseSample {
  func hello() void;
}

class Sample extends BaseSample {
  var name string;

  constructor(name string) {
    this.name = name;
  }

  func hello() void {
    @console.log("hello");
  }
}
js
[alias='console']
interface Console {
  static func log(message string) void;
  static func error(message string) void;
  static func warn(message string) void;
}

Enum

js
define enum Status {
  READY 1,
  RUNNING 2,
  DONE 3
}

API et services

Controller

js
[baseUrl='/api/v1', comment='Contrôleur utilisateur']
controller UserController {
  [method=post, route='/signup']
  func signup(@body req SignupRequest) SignupResponse {
    var res = SignupResponse();
    var n = @mapper.UserMapper.insertUser(req.email, req.password);
    if (n <= 0) {
      res.code = AppErrors.failed.code;
      res.message = AppErrors.failed.message;
      return res;
    }
    res.code = AppErrors.success.code;
    return res;
  }
}

Api (Client)

js
[comment='API utilisateur']
api UserApi from @controller.UserController {}

Cibles front-end

La classe html est convertie en composant front-end.

  • ref var -> état
  • @model -> liaison de saisie
  • @click -> gestion d'événements

DSL écran de login

js
define css {
  text.primary {
    text-gray-800
  }
}

html LoginPage {
  ref var nickname string = "";
  ref var password string = "";

  func login() {
    var res = @api.login(nickname, password);
    @console.log(res.code);
  }

  template {
    div class="@css.text.primary" {
      input @model="nickname";
      input @model="password";
      button @click="login" { "Login" }
    }
  }
}

Widgets Flutter

Flutter utilise la classe widget.

js
widget LoginPage {
  ref var nickname string = "";
  ref var password string = "";

  func login() {
    var res = @api.login(nickname, password);
    @console.log(res.code);
  }

  // définir l'UI dans le bloc widget layout
}

Définitions de ressources

Property

js
define property app {
  baseUrl = "https://api.example.com";
}

define property [profile='dev'] app {
  baseUrl = "https://dev.api.example.com";
}

Message + Message Class

js
define message[locale='en'] app {
  hello: "Hello"
  welcome: "Welcome {name}"
}

message MessageApp from @message.app {
  static func setLocale(locale string) void;
  static var hello string;
  static func welcome(name string) string;
}

Error + Error Class

js
define error[locale='en'] app {
  not_found (100) = "Resource not found"
  server_error (200) = "Internal server error"
}

error AppErrors from @error.app {}

@prompt

@prompt ajoute des indications en langage naturel pour les logiques natives.

js
func main() {
  @prompt `"Hello, World!" en sortie`
}

Exemple de conversion @prompt (Rust/Go/Java)

js
func main(args list<string>) {
  @prompt `Afficher les nombres premiers jusqu'à args[0] (crible d'Ératosthène)`
}
rust
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let n: usize = args.get(1).unwrap_or(&"0".to_string()).parse().unwrap_or(0);
    if n < 2 {
        return;
    }
    let mut is_prime = vec![true; n + 1];
    is_prime[0] = false;
    if n >= 1 {
        is_prime[1] = false;
    }
    let mut p = 2;
    while p * p <= n {
        if is_prime[p] {
            let mut multiple = p * p;
            while multiple <= n {
                is_prime[multiple] = false;
                multiple += p;
            }
        }
        p += 1;
    }
    for i in 2..=n {
        if is_prime[i] {
            println!("{i}");
        }
    }
}
go
package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	n := 0
	if len(os.Args) > 1 {
		if v, err := strconv.Atoi(os.Args[1]); err == nil {
			n = v
		}
	}
	if n < 2 {
		return
	}
	isPrime := make([]bool, n+1)
	for i := range isPrime {
		isPrime[i] = true
	}
	isPrime[0] = false
	if n >= 1 {
		isPrime[1] = false
	}
	for p := 2; p*p <= n; p++ {
		if isPrime[p] {
			for m := p * p; m <= n; m += p {
				isPrime[m] = false
			}
		}
	}
	for i := 2; i <= n; i++ {
		if isPrime[i] {
			fmt.Println(i)
		}
	}
}
java
public class Main {
    public static void main(String[] args) {
        int n = 0;
        if (args.length > 0) {
            try {
                n = Integer.parseInt(args[0]);
            } catch (NumberFormatException ignored) {
                n = 0;
            }
        }
        if (n < 2) {
            return;
        }
        boolean[] isPrime = new boolean[n + 1];
        for (int i = 0; i <= n; i++) {
            isPrime[i] = true;
        }
        isPrime[0] = false;
        if (n >= 1) {
            isPrime[1] = false;
        }
        for (int p = 2; p * p <= n; p++) {
            if (isPrime[p]) {
                for (int m = p * p; m <= n; m += p) {
                    isPrime[m] = false;
                }
            }
        }
        for (int i = 2; i <= n; i++) {
            if (isPrime[i]) {
                System.out.println(i);
            }
        }
    }
}

Bash/PowerShell

shell est converti en fonction de script avec un appel d'exécution.

Bash

  • ajouter set -euo pipefail en tête
  • shell devient une fonction Bash
dsl
shell download_file(url string, dest string) {
  @prompt `Télécharger un fichier avec curl`
}
bash
set -euo pipefail

download_file() {
  local url="$1"
  local dest="$2"
  curl -fsSL "$url" -o "$dest"
}

main() {
  download_file "$@"
}

main "$@"

PowerShell

  • shell devient une function PowerShell
dsl
shell download_file(url string, dest string) {
  @prompt `Télécharger via Invoke-WebRequest`
}
powershell
function Download-File {
  param (
    [Parameter(Mandatory = $true)]
    [string]$Url,

    [Parameter(Mandatory = $true)]
    [string]$Dest
  )

  Invoke-WebRequest -Uri $Url -OutFile $Dest
}

Download-File -Url $Url -Dest $Dest

Vue d’ensemble de conversion

  • analyser les déclarations (controller, mapper, html/widget)
  • générer un squelette par ressource
  • étendre @prompt en code natif

Exemple de bout en bout

js
domain Email string(320);
domain Password string(128);

struct SignupRequest {
  var email Email;
  var password Password;
}

struct SignupResponse {
  var code int32;
  var message string;
}

table user {
  user_id int64 auto;
  email Email unique;
  password Password;
  key(user_id);
}

mapper UserMapper {
  query insertUser(email Email, password Password) int32 {
    insert user(email, password)
    values (:email, :password);
  }
}

define error[locale='en'] app {
  success (0) = "OK"
  failed (100) = "Failed"
}

error AppErrors from @error.app {}

[baseUrl='/api/v1']
controller UserController {
  [method=post, route='/signup']
  func signup(@body req SignupRequest) SignupResponse {
    var res = SignupResponse();
    var n = @mapper.UserMapper.insertUser(req.email, req.password);
    if (n <= 0) {
      res.code = AppErrors.failed.code;
      res.message = AppErrors.failed.message;
      return res;
    }
    res.code = AppErrors.success.code;
    res.message = AppErrors.success.message;
    return res;
  }
}