Skip to content

DSL 参考

Coders 从一个 DSL(.jssp) 生成多语言、多平台代码。声明的资源(controller, mapper, message, error 等)会按平台模板 1:1 转换。

快速开始请见快速开始,概览请见概览

配置说明

config.yml 控制 CLI 的整体行为。

  • entry: 默认解析的 .jssp 入口
  • projects: 定义 platform, name, outPath, entry, target, language, options
  • llmOptions: provider, model, url, apiKey, timeoutSeconds, stream

DSL 概览

Coders DSL 由以下部分组成:

  • 声明: domain, table, entity, mapper, struct, class, controller, api, define
  • 执行: func, main, @prompt
  • 资源: message, error, property, css
  • UI: html + template

数据建模

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;
  }
}

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;
}

应用结构

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 与服务

Controller

js
[baseUrl='/api/v1', comment='用户控制器']
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']
api UserApi from @controller.UserController {}

前端目标

html 类会转换为前端组件。

  • ref var -> 状态
  • @model -> 输入绑定
  • @click -> 事件处理

登录页面 DSL

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" }
    }
  }
}

Flutter 组件

Flutter 使用 widget 类。

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

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

  // widget layout 块中定义 UI
}

资源定义

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 用于用自然语言补充平台相关逻辑。

js
func main() {
  @prompt `输出 "Hello, World!"`
}

@prompt 转换示例 (Rust/Go/Java)

js
func main(args list<string>) {
  @prompt `使用埃拉托色尼筛法输出 args[0] 以内的素数`
}
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 会转换为脚本函数,并包含执行调用。

Bash

  • 顶部加入 set -euo pipefail
  • shell 转为 Bash 函数
dsl
shell download_file(url string, dest string) {
  @prompt `使用 curl 下载文件`
}
bash
set -euo pipefail

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

main() {
  download_file "$@"
}

main "$@"

PowerShell

  • shell 转为 PowerShell function
dsl
shell download_file(url string, dest string) {
  @prompt `用 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

转换概述

  • 解析声明(controller, mapper, html/widget)
  • 按资源生成代码骨架
  • 将 @prompt 展开为原生代码

端到端示例

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;
  }
}