Metadata-Version: 2.4
Name: pykamirsl
Version: 0.1.1
Summary: Robot Scripting Language (RSL) - 카미봇 미니 DSL: 파서/검증기/async 인터프리터
Home-page: https://github.com/kamibot-rsl
Author: Kamibot RSL contributors
License: MIT
Project-URL: Homepage, https://github.com/kamibot-rsl
Project-URL: Source, https://github.com/kamibot-rsl
Keywords: rsl,dsl,robot,kamibot,langgraph,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Education
Classifier: Topic :: Software Development :: Interpreters
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == "langchain"
Dynamic: home-page
Dynamic: requires-python

# pykamrsl

카미봇(Kamibot Pi) 제어용 미니 DSL — **RSL (Robot Scripting Language)** 의
파서, 의미 검증기, async 인터프리터를 담은 **무의존성** 파이썬 패키지.

원래 [phase3-lab/step02_langgraph_rsl](../step02_langgraph_rsl) 안에 있던
`src/rsl/` 를 독립 패키지로 분리한 것이다. LangGraph·MCP·OpenAI 같은 상위
계층 없이도 `parse → validate → interpret` 흐름을 단독으로 테스트할 수 있다.

## 설치

```powershell
# editable install (개발 중 권장)
pip install -e .

# 또는 분리된 폴더 그대로 사용 시
pip install -e d:\wk_Kyrgyz\phase3-lab\pykamrsl
```

`dependencies = []` 이므로 추가로 설치되는 라이브러리가 없다.

LangChain `BaseTool` 과 같이 쓸 거라면 (실제 카미봇 MCP 서버 호출용):

```powershell
pip install -e ".[langchain]"
```

## 빠른 사용 예시 — LLM 없이 RSL 만 다루기

```python
from pykamrsl import parse, validate, RSLInterpreter

source = '''
BEGIN MISSION "장애물 회피"
  SET threshold = 30
  SENSE DISTANCE AS d
  IF d > threshold:
    MOVE BACKWARD CM=10
    BEEP
  ELSE:
    MOVE FORWARD CM=20
  END IF
END MISSION
'''

mission = parse(source)             # → Mission AST
errors = validate(mission)          # [] = 통과
print("validation errors:", errors)

# 인터프리터에 주입할 도구 사전. ainvoke 만 가지면 어떤 객체도 OK.
class FakeTool:
    def __init__(self, name): self.name = name
    async def ainvoke(self, args):
        print(f"[CALL] {self.name}({args})")
        return f"{self.name} 호출 완료"

tools = {n: FakeTool(n) for n in [
    "move_forward_cm", "move_backward_cm", "play_beep",
    "read_object_distance_raw",
]}

import asyncio
log = asyncio.run(RSLInterpreter(tools).arun(mission))
print("\n".join(log))
```

## 패키지 표면

```python
from pykamrsl import (
    parse, ParseError,          # 텍스트 → Mission AST
    validate, ValidationError,  # AST 의미·범위 검증
    RSLInterpreter, RuntimeRSLError, MAX_LOOP_ITERS,
    SYSTEM_PROMPT, build_retry_message,
)
```

`pykamrsl.ast_nodes` 도 export 됨: `Mission`, `Assign`, `Move`, `If`, `Loop`, …

## 도구 인터페이스 (인터프리터 백엔드)

`RSLInterpreter(tools_by_name)` 의 `tools_by_name: dict[str, AsyncTool]` 은
다음 구조적 인터페이스만 만족하면 된다 (`langchain_core.BaseTool` 이 자동 만족):

```python
class AsyncTool(Protocol):
    name: str
    async def ainvoke(self, args: dict) -> Any: ...
```

인터프리터가 호출하는 도구 이름 목록 (있어야 하는 키):

| RSL 문장 | 도구 이름 | 인자 |
|---|---|---|
| `MOVE FORWARD CM=v` | `move_forward_cm` | `distance_cm` |
| `MOVE BACKWARD CM=v` | `move_backward_cm` | `distance_cm` |
| `MOVE * SECONDS=v` | `move_*_seconds` | `seconds` |
| `MOVE * SPEED=v` | `drive_*_continuous` | `speed` |
| `TURN LEFT/RIGHT ANGLE=v` | `turn_*_degrees` | `degrees` |
| `STOP` | `stop_now` | — |
| `WAIT SECONDS=v` | `wait_seconds` | `seconds` |
| `LED RGB r,g,b` | `set_led_rgb` | `r, g, b` |
| `LED PRESET n` | `set_led_preset` | `preset` |
| `BEEP` | `play_beep` | — |
| `MELODY SCALE=s SECONDS=t` | `play_melody_note` | `scale, seconds` |
| `DRAW <shape> SIZE=n` | `draw_<shape>` | `size` |
| `SENSE DISTANCE AS x` | `read_object_distance_raw` | — (`{left,right}` 반환) |
| `SENSE LINE_* AS x` | `read_line_sensors_raw` | — (`{left,center,right}` 반환) |
| `SENSE COLOR AS x` | `read_color_index_raw` | — (`int` 반환) |
| `SENSE BATTERY AS x` | `read_battery_level_raw` | — (`int` 반환) |

## 문법 한눈에 보기

```
mission       := BEGIN MISSION "title" ... END MISSION
statement     := assign | move | turn | stop | wait | led | sound
               | draw | sense | signal | if_block | loop_block | repeat_block
assign        := SET name = value
move          := MOVE FORWARD|BACKWARD CM|SECONDS|SPEED = v
turn          := TURN LEFT|RIGHT ANGLE = v
stop          := STOP
wait          := WAIT SECONDS = v
led           := LED RGB r,g,b | LED PRESET n
sound         := BEEP | MELODY SCALE=s SECONDS=t
draw          := DRAW <shape> SIZE = v
sense         := SENSE <kind> AS var
signal        := SIGNAL "message"
if_block      := IF cond: ... (ELSE: ...)? END IF
loop_block    := LOOP UNTIL|WHILE cond: ... END LOOP
repeat_block  := REPEAT count TIMES: ... END REPEAT
cond          := value <|>|<=|>=|==|!= value | var (truthy)
value         := 정수 | "문자열" | var
```

**수치 허용 범위 (검증기)**

| 매개변수 | 범위 |
|---|---|
| speed | 0~100 |
| cm | 1~200 |
| seconds | 1~30 |
| degrees | 1~360 |
| RGB 채널 | 0~255 |
| LED PRESET | 0~8 |
| DRAW SIZE | 5~50 |
| MELODY SCALE | 1~200 |
| REPEAT count | 1~100 |

**V1 알려진 제약**: 산술·논리 연산자 없음 (비교 6 종만), LOOP / REPEAT 최대 100 회.

## 라이선스

MIT. 학습용으로 자유롭게 수정·재배포 가능.
