Skip to content

Instantly share code, notes, and snippets.

@kishida
Last active December 14, 2025 11:45
Show Gist options
  • Select an option

  • Save kishida/e040f1755c3ddeab307deb5f0319808c to your computer and use it in GitHub Desktop.

Select an option

Save kishida/e040f1755c3ddeab307deb5f0319808c to your computer and use it in GitHub Desktop.
Sui言語のサンプルコードを生成する
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>kis</groupId>
<artifactId>sui-generator</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-http-client-jdk</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<name>Sui Generator</name>
</project>
package kis;
import module java.desktop;
import module java.base;
import java.net.http.HttpClient;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.http.client.jdk.JdkHttpClient;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.PartialThinking;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import tools.jackson.databind.ObjectMapper;
public class SuiGenerator {
// private static final String LM_HOST = "http://192.168.0.3:1234/v1";
// private static final List<String> MODELS = List.of("openai/gpt-oss-120b", "glm-4.6v");
// private static final String API_KEY = "temp";
private static final String LM_HOST = "https://api.ai.sakura.ad.jp/v1";
private static final List<String> MODELS = List.of("gpt-oss-120b", "llm-jp-3.1-8x13b-instruct4");
private static final String API_KEY = System.getenv("SAKURA_API_KEY");
private static final String JSON_FILE = "sui-codes.json";
sealed interface ProblemType {
record Func(int args) implements ProblemType{
@Override
public String getTypeLabel() {
return args > 0 ? "%n引数関数".formatted(args) : "引数なし関数";
}
@Override
public String getTypeName() {
return "Function";
}
@Override
public int getArgNum() {
return args;
}
}
record Script() implements ProblemType{
@Override
public String getTypeLabel() {
return "関数ではないスクリプト";
}
@Override
public String getTypeName() {
return "Script";
}
@Override
public int getArgNum() {
return 0;
}
}
String getTypeLabel();
String getTypeName();
int getArgNum();
}
private static final ProblemType SCRIPT = new ProblemType.Script();
private static final ProblemType[] FUNCS = IntStream.rangeClosed(0, 3)
.mapToObj(ProblemType.Func::new)
.toArray(ProblemType[]::new);
record Problem(ProblemType type, String problem){}
@JsonIgnoreProperties(ignoreUnknown = true)
static class Data {
@JsonProperty("problem")
String problem;
@JsonProperty("type")
String type;
@JsonProperty("arg_num")
int argNum;
@JsonProperty("sui_code")
String suiCode;
@JsonIgnore
Problem problemData;
public Data(Problem problemData) {
this.problemData = problemData;
this.problem = problemData.problem();
this.type = problemData.type().getTypeName();
this.argNum = problemData.type().getArgNum();
}
public Data(Problem problemData, String suiCode) {
this(problemData);
this.suiCode = suiCode;
}
// <editor-fold defaultstate="collapsed" desc="Getters and setters">
public Problem getProblemData() {
return problemData;
}
public String getProblem() {
return problem;
}
public void setProblem(String problem) {
this.problem = problem;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getArgNum() {
return argNum;
}
public void setArgNum(int argNum) {
this.argNum = argNum;
}
public String getSuiCode() {
return suiCode;
}
public void setSuiCode(String suiCode) {
this.suiCode = suiCode;
}
// </editor-fold>
}
static List<Problem> problems;
static List<Data> data;
static int current = 0;
static JButton btnPrev;
static JButton btnNext;
static JTextField txtCurrent;
static JLabel lblTotal;
static JComboBox<String> cmbModel;
static JButton btnGenerate;
static JToggleButton tglAuto;
static JTextArea taProblem;
static JTextArea taSui;
static JButton btnSave;
public static void main(String args[]) throws Exception{
problems = readProblems(PROBLEMS);
data = problems.stream().map(Data::new).toList();
var nimbus = Stream.of(UIManager.getInstalledLookAndFeels())
.filter(lafInfo -> "Nimbus".equals(lafInfo.getName()))
.findAny();
if (nimbus.isPresent()) UIManager.setLookAndFeel(nimbus.get().getClassName());
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
var f = new JFrame("Sui Generator");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
btnPrev = new JButton("<<");
btnNext = new JButton(">>");
txtCurrent = new JTextField(5);
lblTotal = new JLabel(" / " + problems.size());
cmbModel = new JComboBox<>(MODELS.toArray(String[]::new));
btnGenerate = new JButton("生成");
tglAuto = new JToggleButton("自動");
taProblem = new JTextArea();
taSui = new JTextArea();
btnSave = new JButton("保存");
showData(1);
System.out.println(data.get(0).problemData);
btnGenerate.addActionListener(_ -> {
btnGenerate.setEnabled(false);
Thread.ofPlatform().start(() -> eventGenerate());
});
btnPrev.addActionListener(_ -> eventPrev());
btnNext.addActionListener(_ -> eventNext());
txtCurrent.addActionListener(_ -> eventCurrent());
btnSave.addActionListener(_ -> eventSave());
tglAuto.addActionListener(_ -> eventAuto());
var pnlTop = new JPanel(new GridBagLayout());
var pnlTopLeft = new JPanel();
var pnlTopRight = new JPanel(new FlowLayout(FlowLayout.RIGHT));
pnlTop.add(pnlTopLeft, new GridBagConstraints(0, 0, 1, 1, 0.75, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
pnlTop.add(pnlTopRight, new GridBagConstraints(1, 0, 1, 1, 0.25, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
pnlTopLeft.add(btnPrev);
pnlTopLeft.add(txtCurrent);
pnlTopLeft.add(lblTotal);
pnlTopLeft.add(btnNext);
pnlTopLeft.add(cmbModel);
pnlTopLeft.add(btnGenerate);
pnlTopRight.add(tglAuto);
f.add(BorderLayout.NORTH, pnlTop);
var split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
createPanel(taProblem, "Problem"),
createPanel(taSui, "Sui Source"));
f.add(BorderLayout.CENTER, split);
var pnlBottom = new JPanel(new FlowLayout(FlowLayout.RIGHT));
pnlBottom.add(btnSave);
f.add(BorderLayout.SOUTH, pnlBottom);
f.setVisible(true);
split.setDividerLocation(100);
f.setLocationRelativeTo(null);
}
private static JPanel createPanel(JComponent comp, String name) {
var panel = new JPanel(new BorderLayout());
panel.add(BorderLayout.NORTH, new JLabel(name));
panel.add(new JScrollPane(comp,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
return panel;
}
private static List<Problem> readProblems(String problemStrs) {
var pat = Pattern.compile("FUNC\\((\\d+)\\)");
return problemStrs.lines()
.map(line -> line.split(": "))
.map(row -> {
if ("SCRIPT".equals(row[0])) {
return new Problem(SCRIPT, row[1]);
} else {
var mat = pat.matcher(row[0]);
mat.find();
int args = Integer.parseInt(mat.group(1));
return new Problem(FUNCS[args], row[1]);
}
})
.toList();
}
static void finish(Object lock) {
if (lock == null) return;
synchronized (lock) {
lock.notifyAll();
}
btnGenerate.setEnabled(true);
}
static void eventPrev() {
showData(current - 1);
}
static void eventNext() {
showData(current + 1);
}
static void eventCurrent() {
int cur;
try {
cur = Integer.parseInt(txtCurrent.getText());
} catch (NumberFormatException ex) {
txtCurrent.setText(current + "");
return;
}
showData(cur);
}
static void showData(int cur) {
cur = Math.max(1, Math.min(problems.size(), cur));
if (cur == current) return;
storeData(current);
current = cur;
txtCurrent.setText(current + "");
taProblem.setText(data.get(current - 1).getProblem());
taSui.setText(data.get(current - 1).getSuiCode());
}
static void storeData(int cur) {
if (cur < 1 || cur > problems.size()) {
IO.println("Warn: Overflow %d must be %d".formatted(cur, problems.size()));
return;
}
var d = data.get(cur - 1);
d.setProblem(taProblem.getText());
d.setSuiCode(taSui.getText());
}
static void eventSave() {
ObjectMapper mapper = new ObjectMapper();
Path outPath = Path.of(JSON_FILE);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);
try {
Files.writeString(outPath, json, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} catch (IOException ex) {
System.out.println(ex);
}
}
static void eventAuto() {
Thread.ofPlatform().start(() -> {
Object lock = new Object();
for (; current <= data.size() && tglAuto.isSelected(); showData(current + 1)) {
generate(lock);
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException ex) {
System.out.println("interrupted");
}
}
if (current == data.size()) break;
}
tglAuto.setSelected(false);
});
}
static void eventGenerate() {
var lock = new Object();
generate(lock);
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException ex) {
}
}
}
static void generate(Object lock) {
var model = OpenAiStreamingChatModel.builder()
.baseUrl(LM_HOST)
.apiKey(API_KEY)
.modelName(cmbModel.getSelectedItem().toString())
.maxCompletionTokens(2048)
.returnThinking(true)
.httpClientBuilder(JdkHttpClient.builder().httpClientBuilder(
HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1)))
.build();
var systemPrompt =
"""
あなたはSui言語を使ったアプリケーション開発者です。
ユーザーに与えられた課題をSui言語の命令で実装します。
コードブロックに入れたりせず実装のみを出力してください。
Sui言語の仕様は次のとおり
%s
""".formatted(LANG_SPEC);
var userPrompt =
"""
次の仕様を満たす%sを実装してください。
%s
""".formatted(
problems.get(current - 1).type.getTypeLabel(),
taProblem.getText());
var messages = List.of(
SystemMessage.from(systemPrompt),
UserMessage.from(userPrompt));
taSui.setText("");
model.chat(messages, new StreamingChatResponseHandler() {
@Override
public void onPartialThinking(PartialThinking partialThinking) {
IO.print(partialThinking.text());
}
@Override
public void onPartialResponse(String string) {
taSui.append(string);
taSui.setCaretPosition(taSui.getText().length());
}
@Override
public void onCompleteResponse(ChatResponse cr) {
Pattern pattern = Pattern.compile(
"\\s*<\\|begin_of_box\\|>\\s*(.*)\\s*<\\|end_of_box\\|>\\s*",
Pattern.DOTALL
);
var matcher = pattern.matcher(taSui.getText());
if (matcher.find()) {
var result = matcher.group(1);
taSui.setText(result);
}
finish(lock);
}
@Override
public void onError(Throwable thrwbl) {
finish(lock);
}
});
}
private static final String LANG_SPEC =
"""
## Sui言語仕様
Suiは純粋なロジック言語。1行1命令、識別子は連番のみ。
### 命令
= VAR VAL 代入
+ R A B 加算 (R = A + B)
- R A B 減算
* R A B 乗算
/ R A B 除算
% R A B 剰余
< R A B 小なり (R = 1 if A < B else 0)
> R A B 大なり
~ R A B 等価
! R A NOT
& R A B AND
| R A B OR
? COND LABEL 条件ジャンプ(LABELは整数のみ)
@ LABEL 無条件ジャンプ(LABELは整数のみ)
: LABEL ラベル定義(LABELは整数のみ)
# ID ARGC { 関数定義
} 関数終了
$ R FN ARGS... 関数呼び出し
^ VAL return
[ VAR SIZE 配列作成
] R ARR IDX 配列読み取り
{ ARR IDX VAL 配列書き込み
. VAL 出力
, VAR 入力
; COMMENT コメント
### 変数
v0, v1, v2... ローカル変数
g0, g1, g2... グローバル変数(状態、UIからアクセス可能)
a0, a1, a2... 関数引数
### ラベル(重要)
- **LABELは必ず整数**(例: `0`, `1`, `2`)
- `: start_label` のような **文字列ラベルは禁止**
### 例
```sui
; 与えられた整数を3倍する
# 0 1 {
* v0 a0 3
^ v0
}
```
```sui
; Print 1 to 10
= v0 1
: 0
> v1 v0 10
? v1 1
. v0
+ v0 v0 1
@ 0
: 1
```
```sui
; Sum all elements in a list
; Calculate sum of array [10, 20, 30, 40, 50]
; Create array (size 5)
[ g0 5
; Set elements
{ g0 0 10
{ g0 1 20
{ g0 2 30
{ g0 3 40
{ g0 4 50
; Calculate sum
; g1 = sum, v0 = index, g2 = size
= g1 0
= g2 5
= v0 0
; Loop start
: 0
< v1 v0 g2
! v2 v1
? v2 1
; Get arr[v0] and add to sum
] v3 g0 v0
+ g1 g1 v3
; Increment index
+ v0 v0 1
@ 0
; Loop end
: 1
; Output result
. "Sum:"
. g1
```
### 注意
コメントは ; で始める。#でのコメントは禁止
""";
private static final String PROBLEMS =
"""
FUNC(2): 2つの整数A,Bを受け取り、A+Bを返す
FUNC(2): 2つの整数A,Bを受け取り、A-Bを返す
FUNC(2): 2つの整数A,Bを受け取り、A×Bを返す
FUNC(2): 2つの整数A,Bを受け取り、A÷Bの整数商を返す(B≠0)
FUNC(1): 整数Aを受け取り、2×Aを返す
FUNC(1): 整数Aが0なら1、そうでなければ0を返す
FUNC(1): 整数Aが正なら1、負なら-1、0なら0を返す
FUNC(1): 整数Aが偶数なら1、奇数なら0を返す
FUNC(2): 整数A,Bのうち大きい方を返す
FUNC(3): 整数A,B,Cの最大値を返す
FUNC(1): 整数Aの絶対値を返す
FUNC(1): 整数Aが1以上100以下なら1、そうでなければ0を返す
FUNC(1): 整数Aが5の倍数なら1、そうでなければ0を返す
FUNC(1): 整数Aが3または5の倍数なら1、そうでなければ0を返す
FUNC(1): 整数Aが3かつ5の倍数なら1、そうでなければ0を返す
FUNC(1): 整数Aが10未満ならA+10、そうでなければA-10を返す
FUNC(1): 整数A(0–100)が60以上なら1、未満なら0を返す
FUNC(1): 整数Aが0未満または100超なら0、そうでなければAを返す
FUNC(1): 整数Aが1なら10、2なら20、それ以外は0を返す
FUNC(0): 定数42を返す
SCRIPT: 正整数Nを受け取り、1からNまでを順にログ出力する
SCRIPT: 正整数Nを受け取り、Nから1までを順にログ出力する
SCRIPT: 正整数Nを受け取り、1からNまでの合計を計算し最後に出力する
SCRIPT: 正整数Nを受け取り、1からNまでの積を計算し最後に出力する
SCRIPT: 整数Aと回数Nを受け取り、AをN回ログ出力する
SCRIPT: 正整数Nを受け取り、1からNまでの偶数をログ出力する
SCRIPT: 正整数Nを受け取り、1からNまでの奇数をログ出力する
SCRIPT: 正整数Nを受け取り、Nから0まで1ずつ減らしてログ出力する
SCRIPT: 数値列を順に加算し、合計が100を超えた時点で処理を終了し合計を出力する
SCRIPT: 数値列を処理し、最大値が更新されるたびにその値をログ出力する
SCRIPT: 数値列を処理し、最小値が更新されるたびにその値をログ出力する
SCRIPT: 数値列を処理し、直前値と異なった回数を出力する
SCRIPT: 数値列を処理し、値が正であった回数を出力する
SCRIPT: 数値列を処理し、正の値が連続した最大回数を出力する
SCRIPT: 数値列を処理し、最初に正の値が現れた位置(1始まり)を出力する
SCRIPT: 数値列を処理し、最後に正の値が現れた位置(1始まり)を出力する
SCRIPT: 正整数Nを受け取り、偶数回目のループ回数のみログ出力する
SCRIPT: 数値列を処理し、0が入力された時点で終了し、それまでの合計を出力する
SCRIPT: 数値列を処理し、途中で0が出現したかどうかを1/0で出力する
SCRIPT: 数値列を処理し、値が100を超えた回数を出力する
FUNC(2): 加算のみを使ってA×Bを計算して返す
FUNC(2): 減算のみを使ってA÷Bの整数商を返す
FUNC(2): 減算のみを使ってA mod Bを返す
FUNC(2): 掛け算のみを使ってA^Bを返す
FUNC(1): 正整数Nの階乗N!を返す
FUNC(1): フィボナッチ数列の第N項を返す
SCRIPT: 正整数Nを受け取り、フィボナッチ数列をN項ログ出力する
SCRIPT: 正整数Nを受け取り、フィボナッチ数列N項の最大値を出力する
FUNC(2): ユークリッド互除法(剰余)で最大公約数を返す
FUNC(2): 減算法のみで最大公約数を返す
FUNC(2): A×B÷GCD(A,B)で最小公倍数を返す
FUNC(1): 整数Nが素数なら1、そうでなければ0を返す
SCRIPT: 正整数Nを受け取り、N以下の素数の個数を出力する
SCRIPT: 正整数Nを受け取り、N以下の素数をすべてログ出力する
FUNC(1): 整数Nの正の約数の個数を返す
SCRIPT: 整数Nの正の約数をすべてログ出力する
FUNC(1): 整数Nの10進表現の桁数を返す
FUNC(1): 整数Nの各桁の和を返す
FUNC(1): 整数Nの数字を逆順にした整数を返す
FUNC(1): 整数Nが回文数なら1、そうでなければ0を返す
SCRIPT: 1からNまで処理し、3の倍数なら1、それ以外は0をログ出力する
SCRIPT: 1からNまで処理し、5の倍数なら1、それ以外は0をログ出力する
SCRIPT: 1からNまで処理し、3倍数→1、5倍数→2、両方→3、その他→0をログ出力する
FUNC(1): 1からNまでの3の倍数の個数を返す
FUNC(1): 1からNまでの3かつ5の倍数の個数を返す
FUNC(1): 1からNまでの3の倍数の合計を返す
FUNC(1): 1からNまでの5の倍数の最大値を返す(存在しなければ0)
FUNC(1): 1からNまでの3でも5でもない数の個数を返す
FUNC(1): 1からNまでで最初に3の倍数が現れる位置を返す(存在しなければ0)
FUNC(1): 1からNまでで最後に5の倍数が現れる位置を返す(存在しなければ0)
SCRIPT: 状態を0で初期化し、N回 state=(state+1) mod 3 を行い最終状態を出力する
SCRIPT: 状態を0で初期化し、入力が1ならstate=1、0ならstate=0とN回更新し最終状態を出力
SCRIPT: 状態を0で初期化し、N回トグル(0↔1)して最終状態を出力
SCRIPT: 状態を0で初期化し、state=1のときのみ入力値を合計し最終値を出力
SCRIPT: 状態遷移(前回と異なる状態)回数を出力
SCRIPT: 直前状態が0で現在が1になった回数を出力
SCRIPT: 直前状態が1で現在が0になった回数を出力
SCRIPT: 同一状態が連続した最大回数を出力
SCRIPT: 状態1がK回連続した時点で終了し、その時点の入力数を出力
SCRIPT: 初期状態0、入力1→state=1、入力0→state=0、直前状態が1で現在0になった回数(初期状態に戻った回数)を出力
""";
}
@kishida
Copy link
Author

kishida commented Dec 14, 2025

20251214193821

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment