콘텐츠로 이동

어노테이션과 문법 확장

Unibase는 예약어를 작게 유지합니다. HTTP·DB·화면 상태·외부 라이브러리 같은 기능을 새 키워드로 추가하지 않고, 다음 다섯 가지 정해진 확장 지점으로 받아들입니다.

확장 지점 형태 확장하는 내용 이 문서
내장 언어 블록 sql { }, json { }, html { }, css { } 다른 언어를 소스에 직접 쓰고 컴파일러가 검사 내장 언어와 schema
schema 선언 schema 종류 이름 { } 선언형 DSL. 종류(grpc 등)는 예약어가 아닌 이름 schema 선언
어노테이션 @이름(인수) 클래스·필드·메서드에 역할과 메타데이터 부여 어노테이션
외부 클래스 연결 @External, @Bind Java·Go·JavaScript·TypeScript 라이브러리를 Unibase 메서드로 연결 External과 Bind
SDK 선언 native, partial 런타임이 구현하는 타입, 여러 파일로 나눈 클래스 native, partial

예를 들어 Controller, Get, Store 같은 이름은 예약어가 아니라 패키지에 속한 타입 이름입니다. 따라서 이런 이름을 변수·클래스 이름으로 써도 기존 코드의 의미가 바뀌지 않습니다.

이 확장 지점은 컴파일러가 정한 목록입니다. 사용자가 새 키워드·연산자·매크로를 정의하거나 소스를 다시 쓰는 기능은 없습니다. 언어 명세의 “사용자 정의 어노테이션(class X extends Annotation)“도 아직 구현되지 않은 로드맵이므로 현재는 Unknown base class 'Annotation' 오류입니다. 아래 표에 없는 어노테이션은 작성하지 마세요.

어노테이션 형태
@이름
@이름(값)
@이름(인수이름 = 값, 인수이름 = 값, ...)
  • 일반 어노테이션 값은 문자열, 정수(음수 가능), true, false입니다. 실수·식·배열·다른 어노테이션은 쓸 수 없습니다. 예외적으로 @Realizes(REQ_ID)는 요구사항 심볼을, @References(심볼)은 모델 요소 또는 코드 심볼을 받으며 모델링 분석에서 참조를 해석합니다. @Description·@Notes·@Title은 문자열 대신 text block도 받습니다.
  • 이름 없는 값은 인수가 하나일 때만 사용합니다. 인수가 두 개 이상이면 모두 이름 = 값으로 쓰며 같은 이름을 반복할 수 없습니다. 끝 쉼표는 허용하지 않습니다.
  • 형식이 잘못되면 Invalid annotation syntax. 오류입니다.
  • 일반 어노테이션은 클래스·인터페이스·필드·메서드·생성자·매개변수 앞에 둘 수 있지만, 의미와 허용 위치는 아래 표 및 생성 대상의 계약을 따릅니다. @Realizes·@References는 타입(enum 포함)·메서드·생성자·필드의 추적 관계입니다. schema는 본문 관계 문법을 사용합니다.

unic check에서 사용할 수 있는 어노테이션

섹션 제목: “unic check에서 사용할 수 있는 어노테이션”
어노테이션 위치 의미
@Service 클래스 서비스 역할 표시. 인수 없음
@Store, @Store(reactivity = "explicit") 클래스 화면 전체가 공유하는 상태 저장소
@Reactive @Store 클래스의 필드 값이 바뀌면 화면을 다시 그리는 필드
@LocalStorage(key = "…", version = N) @Store 클래스의 필드 브라우저 저장소에 보존하는 필드
@External(…), @Bind(…) 클래스, 메서드 본문 External과 Bind
@Realizes(REQ_ID) 타입(enum 포함), 메서드, 생성자, 필드 요구사항 심볼과 추적 관계. 프로젝트 분석에서 참조 해석
@References(심볼) @Realizes와 같은 위치 다른 모델 요소(테이블·QA case·화면 등)나 코드 심볼과의 참조 관계. layout·diagram은 가리킬 수 없음
@Description("…"), @Notes("…"), @Title("…") @Realizes와 같은 위치. 매개변수에는 사용 불가 선언의 설명·메모·표시 제목. 화면 페이지 클래스는 페이지 제목을 @Title로 씀
@Async, @Idempotent, @Transaction("READ_ONLY" | "REQUIRED" | "REQUIRES_NEW") @Description과 같은 위치. 주로 Controller handler·Service 연산 설계 정책 기록. 없으면 비동기 아님·멱등 아님·트랜잭션 없음(NONE)
@Stereotype, @Status, @Policy, @Relation, @Step, @Param @Description과 같은 위치. enum과 partial 조각에도 사용 가능 설계 메모 기록

추적·설계 정책 어노테이션(@Realizes, @References, @Description, @Notes, @Title, @Async, @Idempotent, @Transaction)은 역할 어노테이션 옆에 둘 수 있으며 역할로 세지 않습니다. 실행 의미가 없고 생성 코드를 바꾸지 않습니다. @Realizes·@References 외의 어노테이션은 한 선언에 한 번만 쓸 수 있으며, 빈 설명은 어노테이션을 생략해 표현합니다. 소스 분석은 각 선언의 description·notes·title과 async·idempotent·transactionPolicy를 보고합니다.

설명과 설계 정책
@Description("주문 화면에 표시하는 행")
@Notes("""
읽기 전용입니다.
행은 주문 Mapper에서 읽습니다.
""")
public class OrderRow {
@Description("줄 수")
public int count;
}
@Handler
@Idempotent
@Transaction("READ_ONLY")
public OrderView order(string orderId) { ... }
잘못된 사용 진단
표에 없는 어노테이션, @Store 밖의 @Reactive Annotation semantic integration is not yet supported.
클래스에 역할 어노테이션 두 개, 또는 @Service("x")처럼 인수 사용 Executable role requires one argument-free class marker.
타입 선언이 아닌 곳의 최상위 어노테이션 Annotation must precede a class or interface declaration.

@Stereotype, @Status, @Policy, @Relation, @Step, @Param은 클래스·Controller·Service의 설계 메모를 소스에 보관합니다. @Description과 같은 위치에 역할 어노테이션과 함께 둘 수 있으며, 다른 어노테이션과 달리 enum과 partial 클래스 조각에도 붙일 수 있습니다. 실행 의미는 없습니다.

설계 메모
@Stereotype("ENTITY")
@Policy(security = "메서드별 OAuth2 scope", exception = "오류는 표준 응답으로 변환")
public class Order {
@Relation(target = "com.shop.OrderItem", type = "COMPOSITION", sourceMultiplicity = "1", targetMultiplicity = "1..*")
public List<OrderItem> items;
@Status(201)
@Param(name = "request", description = "생성할 주문")
@Step(kind = "VALIDATE", description = "요청 검증", expression = "validator.validate(request)")
@Step(kind = "RETURN", expression = "result")
public OrderView create(CreateOrder request) { ... }
}
어노테이션 인수
@Stereotype 문자열 하나
@Status 세 자리 HTTP 상태 하나. handler의 성공 상태
@Policy security, exception, validation, transaction(읽는 사람을 위한 정책 설명)과 basePath(설계의 경로 접두어) 중 필요한 것. 검사되는 트랜잭션 정책은 @Transaction
@Relation 필수 target(관련 타입의 전체 이름)·type, 선택 name·description·sourceMultiplicity·targetMultiplicity. 반복 가능
@Step 필수 kind, 선택 description·expression·assignTo·condition. 실행 순서대로 반복
@Param 필수 name·description. 메서드·생성자 매개변수의 설명이며 반복 가능. 매개변수 자체에는 붙이지 않음

@Status 외의 인수는 모두 이름 있는 문자열입니다. 소스 분석은 각 선언의 stereotype·status·policy·relations·steps·params를 보고합니다.

전체 소스: Settings.uni
import unibase.core.ui.*;
import unibase.core.ui.window.*;
import unibase.core.ui.web.*;
@Store(reactivity = "explicit")
class Preferences {
@Reactive
@LocalStorage(key = "theme", version = 1)
public string theme = "light";
@Reactive
public int visits = 0;
public string label = "draft";
}
public class SettingsPage extends HtmlPage {
public Widget build(HtmlRouteContext route) {
return html {
<section>
<p>Theme {Preferences().theme}</p>
<input type="text" ub:bind:value="Preferences().theme">
</section>
};
}
}
public class AppRouter extends HtmlRouter {
public List<HtmlRoute> buildRoutes() {
return [{ path: "/", page: SettingsPage() }];
}
}
public class Application {
public static void main() {
HtmlApplication application = HtmlApplication(AppRouter());
Window window = Window("Settings", 640.0, 480.0, "en", "ltr", application.build());
window.show();
}
}
  • @Store는 클래스의 유일한 실행 역할 어노테이션이어야 하며 상속·인터페이스 구현·타입 매개변수·정적 필드·매개변수 있는 생성자를 둘 수 없습니다. 실행 의미가 없는 @Realizes 관계는 별개입니다. 어디서나 Preferences()로 같은 저장소를 사용합니다.
  • reactivity = "explicit"이면 @Reactive를 붙인 필드만 화면 갱신 대상입니다. 생략하면 변경 가능한 모든 인스턴스 필드가 대상입니다.
  • @Reactive·@LocalStorage 필드는 public이고 final이 아니며 초기값이 있어야 합니다. 타입은 string, int, double, bool 중 하나입니다(@LocalStorage는 List<string>도 가능).
  • @LocalStorage에는 key(1~256자, 프로그램에서 고유)와 version(양의 정수)을 모두 지정합니다.
  • 입력 요소와 저장소 필드를 연결하는 ub:bind:value="Store().필드", ub:bind:checked="Store().필드"는 따옴표 안에 식을 씁니다. string 필드는 type="text" 등 텍스트 입력, int·double 필드는 type="number", bool 필드는 type="checkbox"와 checked에 연결합니다.
  • 오류는 FRONTEND_STORE_CONTRACT:로 시작합니다(LocalStorage requires key and version 등).

서버 프로젝트 생성 전용 어노테이션

섹션 제목: “서버 프로젝트 생성 전용 어노테이션”

ERWeb의 서버·API 프로젝트 생성 경로는 다음 어노테이션을 추가로 읽습니다. 이 어노테이션은 단일 파일 unic check·unic run의 대상이 아니며 실행 프로그램 소스에 쓰면 거절됩니다.

어노테이션 위치 역할
@Api 인터페이스 HTTP API 계약
@Get, @Post, @Put, @Patch, @Delete, @Head, @Options (path = "…") @Api 메서드 HTTP 메서드와 경로
@Path, @Query, @Header, @Cookie, @Body @Api 메서드 매개변수 요청 값의 위치
@Response(status = "200", …), @Public, @Security("정책") @Api 메서드 응답·인증 메타데이터
@Controller, @Handler 클래스, 메서드 @Api를 구현하는 서버 클래스
@Schema, @Field(required = …) 클래스, 필드 요청·응답 데이터 모델
@Mapper, @Mapper(dataSource = "…") 클래스 SQL 메서드 모음
@Environment, @SecurityPolicy, @Permission, @Asset, @Theme 클래스 환경·보안·자원 설정

@Controller·@Service 클래스는 생성자로 받은 @Mapper를 통해서만 DB에 접근합니다. 이 클래스의 메서드에 직접 SQL 문장을 쓰거나 Database·SqlStatement(예: Database.open, prepareSelect)를 사용하면 클래스와 메서드를 지목하는 검사 오류입니다. 길이·형식·범위 같은 입력 검사는 SQL이 아니라 코드에 둡니다. 일반 클래스와 콘솔 프로그램은 직접 SQL과 unibase.db API를 계속 사용할 수 있습니다.

@Mapper 클래스의 각 메서드는 public 인스턴스 메서드이며, 본문은 매개변수를 :이름으로 연결한 직접 SQL 문장 하나입니다.

@Mapper 형태 (서버 프로젝트 생성)
@Mapper
public class OrdersMapper {
public OrderRow find(int id) {
SELECT id, name FROM orders WHERE id = :id;
}
public int create(int id, string name) {
INSERT INTO orders (id, name) VALUES (:id, :name);
}
}

SELECT는 데이터 클래스·List<데이터 클래스>·단일 값으로, INSERT·UPDATE·DELETE는 변경된 행 수 int로 변환됩니다. 프로젝트 구성·생성 대상·전달 타입은 코드 생성 대상과 실행에서 이어서 확인합니다.

@External은 Unibase 클래스를 외부 언어의 라이브러리와 연결하고, @Bind는 대상 언어별 메서드 본문을 제공합니다. Bind 본문은 대상 언어 코드 그대로 보존되며 Unibase 문장으로 해석하지 않습니다.

전체 소스: TextTools.uni
package sample;
@External(language="java", dependency="standard", import="java.util.Locale")
@External(language="javascript", dependency="standard", import="node:path", importKind="namespace", alias="path")
public class TextTools {
public static string format(string text, string locale) {
@Bind(language="java") {
var options = Locale.forLanguageTag(locale);
return text.strip() + ":" + options.getLanguage();
}
@Bind(language="javascript") {
const options = { locale, text: path.basename(text.trim()) };
return options.text + ":" + options.locale;
}
}
}
class Main {
static void main() {
string formatted = TextTools.format("hello", "ko");
print(formatted);
}
}
@External 인수 설명
language (필수) java, go, javascript, typescript
framework, platform, buildSystem 대상 선택 조건. framework는 vue·react·svelte, JavaScript·TypeScript 전용
import (필수) 가져올 타입·모듈
package, version 외부 의존성. dependency="standard"이면 생략
alias, importKind, name 가져온 이름. importKind는 named·default·namespace (JavaScript·TypeScript)
  • 대상마다 @External을 반복합니다. @Bind에는 language 등 선택 조건만 씁니다.
  • External 클래스의 메서드는 public이며 본문에는 @Bind 블록만 둡니다 (EXTERNAL_CLASS: External method body must contain only Bind blocks). 본문 없는 public static 메서드는 가져온 이름의 같은 메서드를 직접 호출합니다.
  • 상속·제네릭·필드·native·const는 사용할 수 없습니다. 생성되는 연결 코드는 현재 public static 메서드와 string, bool, int, long, float, double, void 경계만 지원합니다.
  • 실제 연결 코드는 Spring Boot와 Vue·React·Svelte 화면 코드 생성에서 만들어집니다. Bind 본문의 대상 언어 문법은 해당 언어의 빌드에서 검사됩니다.
public native class Clock {
public static native long now();
}
  • native 클래스·메서드는 본문을 Unibase 런타임이나 SDK가 제공합니다. 본문 없이 ;로 끝나는 메서드는 native에만 허용됩니다.
  • Standard 라이브러리의 Console, Async, Future 등이 native 선언입니다. native 클래스에는 자동 기본 생성자가 없습니다.
  • 일반 사용자 코드에서 native 선언을 만들면 문법 검사는 통과하지만 실행 시 런타임이 해당 구현을 제공해야 합니다. 구현이 없으면 실행 단계에서 Native method unavailable 오류가 납니다. 일반 프로그램에서는 사용하지 마세요.
Home.uni
public class Home extends HtmlPage {
private int count = 0;
}
Home.html.uni
partial class Home {
public Widget build(HtmlRouteContext route) {
return html { <p>{this.count}</p> };
}
}
  • 한 클래스를 본문 하나와 partial 조각 여러 개로 나눕니다. 화면 코드 생성 프로젝트에서 Home.uni·Home.html.uni·Home.css.uni처럼 역할별 파일로 나눌 때 사용합니다.
  • 조각에는 접근 수식어·extends·implements·타입 매개변수·어노테이션을 쓰지 않습니다. 이것들은 본문 선언에만 둡니다 (Partial fragments must leave visibility, inheritance, type parameters and annotations on the class body). 설계 메모 어노테이션은 예외로 조각에도 붙일 수 있습니다.
  • 조각 사이에 같은 필드·메서드를 선언하면 Duplicate partial class field·Duplicate partial class method 오류, 본문이 없으면 Partial class body is missing 오류입니다. native·External 클래스는 나눌 수 없습니다.
  • 조각의 병합은 여러 파일을 함께 처리하는 프로젝트에서 이루어집니다. 한 파일 안에 본문과 조각을 함께 두면 Duplicate type declaration 오류입니다.

다음은 언어 명세에 계획으로만 있는 확장입니다. 현재 컴파일러에서 사용할 수 없습니다.

계획 현재 상태
class X extends Annotation로 사용자 어노테이션 정의 Annotation 기반 타입 없음
regex { }, xml { } 식 블록 내장 언어로 인식하지 않음
schema graphql/http/validation/state/workflow/search 구문만 통과, 검사·생성 없음
gRPC 생성 클라이언트·서버 API message·service 이름 등록까지만 지원
JSON 블록 안의 변수 보간 미지원

for (… in …) 컬렉션 순회는 로드맵이 아니라 현행 생성기가 대상 언어의 순회로 변환하는 반복문입니다. 다른 생성 대상의 문장 제한은 별도로 확인합니다.