Skip to content

DSL リファレンス

Codersは1つの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 はDSLにないネイティブ処理を自然言語で補足するためのヒントです。

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