gglang 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gglang-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gglang authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gglang-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: gglang
3
+ Version: 0.2.0
4
+ Summary: 줴줴이랭: "줴줴이야~" 밈으로 만든 난해한 프로그래밍 언어 (엄랭 오마주)
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://gglang.jihwan.kim
7
+ Keywords: esolang,esoteric,korean,interpreter
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Software Development :: Interpreters
10
+ Classifier: Natural Language :: Korean
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
15
+
16
+ # 줴줴이랭 (gglang)
17
+
18
+ "줴줴이야~" 밈으로 만든 난해한 프로그래밍 언어. [엄랭](https://github.com/rycont/umjunsik-lang) 오마주.
19
+
20
+ - 플레이그라운드: https://gglang.jihwan.kim
21
+ - 확장자: `.ggya`
22
+
23
+ ## 설치
24
+
25
+ ```bash
26
+ pip install gglang
27
+ ```
28
+
29
+ ## 실행
30
+
31
+ ```bash
32
+ gglang hello.ggya
33
+ # 또는
34
+ python -m gglang hello.ggya
35
+ ```
36
+
37
+ ## 문법 요약
38
+
39
+ | 코드 | 뜻 |
40
+ |---|---|
41
+ | `아뇨` | 1. `아뇨아뇨아뇨`는 3 |
42
+ | 붙여 쓰기 | 더하기. `아뇨아뇨쥐~랄` → 2 + 변수 |
43
+ | 띄어 쓰기 | 곱하기. `아뇨아뇨 아뇨아뇨아뇨` → 2 × 3 |
44
+ | `엄` | 바로 뒤 원소의 부호 뒤집기. `엄아뇨` → -1 |
45
+ | `쥐~랄`, `쥐~~랄`, … | 변수 (물결 수가 변수 번호) |
46
+ | `쥐~랄 식` | 대입 |
47
+ | `아 예?` | 정수 입력 |
48
+ | `티~원 식` | 숫자 출력 |
49
+ | `티~원` | 공백 출력 |
50
+ | `티~~원 식` | 유니코드 문자 하나 출력 |
51
+ | `티~~원` | 줄바꿈 |
52
+ | `요이~` … `요이~` | 반복 (물결 수가 같은 것끼리 짝) |
53
+ | `줴줴이야 식` | 식이 0이면 반복 탈출 |
54
+
55
+ ## 예제
56
+
57
+ `GG` 출력:
58
+
59
+ ```
60
+ 쥐~랄 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨
61
+ 쥐~랄 쥐~랄엄아뇨
62
+ 티~~원 쥐~랄
63
+ 티~~원 쥐~랄
64
+ 티~~원
65
+ ```
66
+
67
+ ## 라이선스
68
+
69
+ MIT
gglang-0.2.0/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # 줴줴이랭 (gglang)
2
+
3
+ "줴줴이야~" 밈으로 만든 난해한 프로그래밍 언어. [엄랭](https://github.com/rycont/umjunsik-lang) 오마주.
4
+
5
+ - 플레이그라운드: https://gglang.jihwan.kim
6
+ - 확장자: `.ggya`
7
+
8
+ ## 설치
9
+
10
+ ```bash
11
+ pip install gglang
12
+ ```
13
+
14
+ ## 실행
15
+
16
+ ```bash
17
+ gglang hello.ggya
18
+ # 또는
19
+ python -m gglang hello.ggya
20
+ ```
21
+
22
+ ## 문법 요약
23
+
24
+ | 코드 | 뜻 |
25
+ |---|---|
26
+ | `아뇨` | 1. `아뇨아뇨아뇨`는 3 |
27
+ | 붙여 쓰기 | 더하기. `아뇨아뇨쥐~랄` → 2 + 변수 |
28
+ | 띄어 쓰기 | 곱하기. `아뇨아뇨 아뇨아뇨아뇨` → 2 × 3 |
29
+ | `엄` | 바로 뒤 원소의 부호 뒤집기. `엄아뇨` → -1 |
30
+ | `쥐~랄`, `쥐~~랄`, … | 변수 (물결 수가 변수 번호) |
31
+ | `쥐~랄 식` | 대입 |
32
+ | `아 예?` | 정수 입력 |
33
+ | `티~원 식` | 숫자 출력 |
34
+ | `티~원` | 공백 출력 |
35
+ | `티~~원 식` | 유니코드 문자 하나 출력 |
36
+ | `티~~원` | 줄바꿈 |
37
+ | `요이~` … `요이~` | 반복 (물결 수가 같은 것끼리 짝) |
38
+ | `줴줴이야 식` | 식이 0이면 반복 탈출 |
39
+
40
+ ## 예제
41
+
42
+ `GG` 출력:
43
+
44
+ ```
45
+ 쥐~랄 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨
46
+ 쥐~랄 쥐~랄엄아뇨
47
+ 티~~원 쥐~랄
48
+ 티~~원 쥐~랄
49
+ 티~~원
50
+ ```
51
+
52
+ ## 라이선스
53
+
54
+ MIT
@@ -0,0 +1 @@
1
+ """줴줴이랭 인터프리터."""
@@ -0,0 +1,21 @@
1
+ import sys
2
+
3
+ from gglang.runtime import GGError, GGya
4
+
5
+
6
+ def main():
7
+ if len(sys.argv) != 2:
8
+ print("사용법: python -m gglang 파일.ggya", file=sys.stderr)
9
+ sys.exit(2)
10
+ with open(sys.argv[1], encoding="utf-8") as f:
11
+ code = f.read()
12
+ try:
13
+ GGya().run(code)
14
+ except GGError as e:
15
+ sys.stdout.flush()
16
+ print(f"아 예? 엄 줴줴이야~ {e}", file=sys.stderr)
17
+ sys.exit(1)
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
@@ -0,0 +1,175 @@
1
+ import re
2
+ import sys
3
+
4
+ # 원소: 엄(부호) 0개 이상 + 숫자/변수/입력
5
+ ATOM = re.compile(r"(엄*)((?:아뇨)+|쥐~+랄|\x01)")
6
+ INT_IN = "\x01" # 아 예?
7
+
8
+
9
+ class GGError(Exception):
10
+ def __init__(self, line_no, msg):
11
+ super().__init__(f"{line_no}번째 줄: {msg}")
12
+
13
+
14
+ class GGya:
15
+ def __init__(self):
16
+ self.vars = {}
17
+ self.lines = []
18
+ self.pair = {}
19
+ self.outer = {}
20
+ self.no = 0 # 지금 보고 있는 줄 번호 (에러 메시지용)
21
+
22
+ def error(self, msg):
23
+ raise GGError(self.no, msg)
24
+
25
+ def type(self, line):
26
+ s = line.rstrip(" ")
27
+ if s == "":
28
+ return "EMPTY"
29
+ if re.fullmatch(r"요이~+", s):
30
+ return "LOOP"
31
+ if s == "줴줴이야" or s.startswith("줴줴이야 "):
32
+ return "BREAK"
33
+ if s == "티~원":
34
+ return "SPACE"
35
+ if s.startswith("티~원 "):
36
+ return "NUM"
37
+ if s == "티~~원":
38
+ return "NEWLINE"
39
+ if s.startswith("티~~원 "):
40
+ return "CHAR"
41
+ if re.match(r"쥐~+랄 ", s):
42
+ return "VAR"
43
+ return "UNKNOWN"
44
+
45
+ def check(self):
46
+ stack = []
47
+ for i, line in enumerate(self.lines):
48
+ self.no = i + 1
49
+ s = line.rstrip(" ")
50
+ t = self.type(line)
51
+ if t == "UNKNOWN":
52
+ if re.fullmatch(r"쥐~+랄", s):
53
+ self.error("대입할 값이 없음")
54
+ self.error(f"모르는 명령 '{s}'")
55
+ elif t == "LOOP":
56
+ n = s.count("~")
57
+ if stack and stack[-1][0] == n:
58
+ _, open_i = stack.pop()
59
+ self.pair[open_i] = i
60
+ self.pair[i] = open_i
61
+ elif any(m == n for m, _ in stack):
62
+ self.error(f"요이{'~' * n} 짝이 엇갈림")
63
+ else:
64
+ stack.append((n, i))
65
+ elif t == "BREAK":
66
+ self.outer[i] = stack[-1][1] if stack else None
67
+ if s != "줴줴이야":
68
+ self.syntax(s[5:])
69
+ elif t == "NUM":
70
+ self.syntax(s[4:])
71
+ elif t == "CHAR":
72
+ self.syntax(s[5:])
73
+ elif t == "VAR":
74
+ self.syntax(s.split(" ", 1)[1])
75
+ if stack:
76
+ n, open_i = stack[0]
77
+ self.no = open_i + 1
78
+ self.error(f"요이{'~' * n} 짝이 없음")
79
+
80
+ def syntax(self, expr):
81
+ """식 구문만 검사한다. 틀리면 GGError."""
82
+ expr = expr.strip(" ")
83
+ if "\t" in expr:
84
+ self.error("탭은 들여쓰기에만 쓸 수 있음")
85
+ while expr.startswith("엄 "):
86
+ expr = expr[2:].strip(" ")
87
+ if expr in ("", "엄"):
88
+ self.error("'엄' 뒤에 값이 없음")
89
+ if re.search(r"아 예\?{2,}", expr):
90
+ self.error("'아 예' 뒤 물음표는 1개만")
91
+ expr = expr.replace("아 예?", INT_IN)
92
+ for term in expr.split(" "):
93
+ if term == "":
94
+ continue
95
+ if term == "엄":
96
+ self.error("부정 '엄'은 식 맨 앞에만 쓸 수 있음")
97
+ pos = 0
98
+ while pos < len(term):
99
+ m = ATOM.match(term, pos)
100
+ if m:
101
+ pos = m.end()
102
+ continue
103
+ rest = term[pos:].lstrip("엄")
104
+ if rest == "":
105
+ self.error("'엄' 뒤에 값이 없음")
106
+ self.error(f"식에 쓸 수 없는 글자 '{rest[0]}'")
107
+
108
+ def run(self, code):
109
+ self.lines = []
110
+ for raw in code.split("\n"):
111
+ raw = raw.removesuffix("\r")
112
+ self.lines.append(raw.lstrip(" \t"))
113
+ self.check()
114
+ i = 0
115
+ while i < len(self.lines):
116
+ self.no = i + 1
117
+ line = self.lines[i]
118
+ s = line.rstrip(" ")
119
+ t = self.type(line)
120
+ if t == "SPACE":
121
+ print(" ", end="")
122
+ elif t == "CHAR":
123
+ n = self.value(s[5:])
124
+ if not (0 <= n <= 0x10FFFF) or 0xD800 <= n <= 0xDFFF:
125
+ self.error(f"문자로 바꿀 수 없는 수 '{n}'")
126
+ print(chr(n), end="")
127
+ elif t == "NEWLINE":
128
+ print()
129
+ elif t == "NUM":
130
+ print(self.value(s[4:]), end="")
131
+ elif t == "VAR":
132
+ name, expr = s.split(" ", 1)
133
+ self.vars[name.count("~")] = self.value(expr)
134
+ elif t == "LOOP":
135
+ if self.pair[i] < i:
136
+ i = self.pair[i]
137
+ elif t == "BREAK":
138
+ expr = s[5:]
139
+ if expr == "" or self.value(expr) == 0:
140
+ if self.outer[i] is None:
141
+ return
142
+ i = self.pair[self.outer[i]]
143
+ i += 1
144
+
145
+ def read(self):
146
+ line = sys.stdin.readline()
147
+ if line == "":
148
+ self.error("입력이 없음")
149
+ return line.removesuffix("\n").removesuffix("\r")
150
+
151
+ def value(self, expr):
152
+ expr = expr.strip(" ")
153
+ if expr.startswith("엄 "):
154
+ return 1 if self.value(expr[2:]) == 0 else 0
155
+ expr = expr.replace("아 예?", INT_IN)
156
+ result = 1
157
+ for term in expr.split(" "):
158
+ if term == "":
159
+ continue
160
+ total = 0
161
+ for signs, atom in ATOM.findall(term):
162
+ if atom.startswith("쥐"):
163
+ n = self.vars.get(atom.count("~"), 0)
164
+ elif atom == INT_IN:
165
+ line = self.read()
166
+ if not re.fullmatch(r"[+-]?[0-9]+", line.strip(" ")):
167
+ self.error(f"정수가 아님 '{line}'")
168
+ n = int(line.strip(" "))
169
+ else:
170
+ n = atom.count("아뇨")
171
+ if len(signs) % 2:
172
+ n = -n
173
+ total += n
174
+ result *= total
175
+ return result
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: gglang
3
+ Version: 0.2.0
4
+ Summary: 줴줴이랭: "줴줴이야~" 밈으로 만든 난해한 프로그래밍 언어 (엄랭 오마주)
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://gglang.jihwan.kim
7
+ Keywords: esolang,esoteric,korean,interpreter
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Software Development :: Interpreters
10
+ Classifier: Natural Language :: Korean
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Dynamic: license-file
15
+
16
+ # 줴줴이랭 (gglang)
17
+
18
+ "줴줴이야~" 밈으로 만든 난해한 프로그래밍 언어. [엄랭](https://github.com/rycont/umjunsik-lang) 오마주.
19
+
20
+ - 플레이그라운드: https://gglang.jihwan.kim
21
+ - 확장자: `.ggya`
22
+
23
+ ## 설치
24
+
25
+ ```bash
26
+ pip install gglang
27
+ ```
28
+
29
+ ## 실행
30
+
31
+ ```bash
32
+ gglang hello.ggya
33
+ # 또는
34
+ python -m gglang hello.ggya
35
+ ```
36
+
37
+ ## 문법 요약
38
+
39
+ | 코드 | 뜻 |
40
+ |---|---|
41
+ | `아뇨` | 1. `아뇨아뇨아뇨`는 3 |
42
+ | 붙여 쓰기 | 더하기. `아뇨아뇨쥐~랄` → 2 + 변수 |
43
+ | 띄어 쓰기 | 곱하기. `아뇨아뇨 아뇨아뇨아뇨` → 2 × 3 |
44
+ | `엄` | 바로 뒤 원소의 부호 뒤집기. `엄아뇨` → -1 |
45
+ | `쥐~랄`, `쥐~~랄`, … | 변수 (물결 수가 변수 번호) |
46
+ | `쥐~랄 식` | 대입 |
47
+ | `아 예?` | 정수 입력 |
48
+ | `티~원 식` | 숫자 출력 |
49
+ | `티~원` | 공백 출력 |
50
+ | `티~~원 식` | 유니코드 문자 하나 출력 |
51
+ | `티~~원` | 줄바꿈 |
52
+ | `요이~` … `요이~` | 반복 (물결 수가 같은 것끼리 짝) |
53
+ | `줴줴이야 식` | 식이 0이면 반복 탈출 |
54
+
55
+ ## 예제
56
+
57
+ `GG` 출력:
58
+
59
+ ```
60
+ 쥐~랄 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨 아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨아뇨
61
+ 쥐~랄 쥐~랄엄아뇨
62
+ 티~~원 쥐~랄
63
+ 티~~원 쥐~랄
64
+ 티~~원
65
+ ```
66
+
67
+ ## 라이선스
68
+
69
+ MIT
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ gglang/__init__.py
5
+ gglang/__main__.py
6
+ gglang/runtime.py
7
+ gglang.egg-info/PKG-INFO
8
+ gglang.egg-info/SOURCES.txt
9
+ gglang.egg-info/dependency_links.txt
10
+ gglang.egg-info/entry_points.txt
11
+ gglang.egg-info/top_level.txt
12
+ tests/test_golden.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gglang = gglang.__main__:main
@@ -0,0 +1 @@
1
+ gglang
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gglang"
7
+ version = "0.2.0"
8
+ description = "줴줴이랭: \"줴줴이야~\" 밈으로 만든 난해한 프로그래밍 언어 (엄랭 오마주)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["esolang", "esoteric", "korean", "interpreter"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Topic :: Software Development :: Interpreters",
17
+ "Natural Language :: Korean",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://gglang.jihwan.kim"
22
+
23
+ [project.scripts]
24
+ gglang = "gglang.__main__:main"
25
+
26
+ [tool.setuptools]
27
+ packages = ["gglang"]
gglang-0.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,49 @@
1
+ """골든 테스트: tests/golden/*.ggya 를 실행해서 기대 결과와 비교한다.
2
+
3
+ 파일 규칙 (이름이 같은 파일끼리 한 세트)
4
+ - 이름.ggya : 실행할 프로그램
5
+ - 이름.in : 표준 입력 (없으면 빈 입력)
6
+ - 이름.out : 기대 표준 출력 (없으면 출력이 비어 있어야 함)
7
+ - 이름.err : 기대 에러 메시지 (있으면 종료 코드 1, 없으면 0)
8
+ """
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ ROOT = Path(__file__).resolve().parents[1]
17
+ GOLDEN = Path(__file__).resolve().parent / "golden"
18
+ CASES = sorted(p.stem for p in GOLDEN.glob("*.ggya"))
19
+
20
+
21
+ def read(path):
22
+ return path.read_text(encoding="utf-8") if path.exists() else None
23
+
24
+
25
+ @pytest.mark.parametrize("name", CASES)
26
+ def test_golden(name):
27
+ stdin = read(GOLDEN / f"{name}.in") or ""
28
+ want_out = read(GOLDEN / f"{name}.out") or ""
29
+ want_err = read(GOLDEN / f"{name}.err")
30
+
31
+ env = dict(os.environ, PYTHONIOENCODING="utf-8")
32
+ proc = subprocess.run(
33
+ [sys.executable, "-m", "gglang", str(GOLDEN / f"{name}.ggya")],
34
+ input=stdin.encode("utf-8"),
35
+ capture_output=True,
36
+ cwd=ROOT,
37
+ env=env,
38
+ timeout=5,
39
+ )
40
+ out = proc.stdout.decode("utf-8")
41
+ err = proc.stderr.decode("utf-8")
42
+
43
+ assert out == want_out, "표준 출력이 다름"
44
+ if want_err is None:
45
+ assert err == "", "에러가 나면 안 됨"
46
+ assert proc.returncode == 0, "종료 코드는 0이어야 함"
47
+ else:
48
+ assert err.rstrip("\n") == want_err.rstrip("\n"), "에러 메시지가 다름"
49
+ assert proc.returncode == 1, "에러 종료 코드는 1이어야 함"