@junnyontop-pixel/neo-app 1.1.10 → 1.1.11

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.
@@ -1,72 +1,83 @@
1
1
  export class NeoParser {
2
2
  static parse(source) {
3
3
  let scriptContent = "";
4
- // 1. 스크립트 영역 추출 (@Script { ... })
5
4
  const scriptMatch = source.match(/@Script\s*\{([\s\S]*?)\}/);
6
- if (scriptMatch) {
7
- scriptContent = scriptMatch[1].trim();
8
- }
5
+ if (scriptMatch) scriptContent = scriptMatch[1].trim();
9
6
 
10
- // 2. 태그 블록 분석 로직
11
- // 정규식 설명: @아이디:태그 { 내부내용 } 을 줄바꿈 상관없이([\\s\\S]*?) 추출
12
- const tagRegex = /@(\w+):(\w+)\s*\{([\s\S]*?)\}/g;
13
- let match;
14
- let root = null;
15
- const stack = [];
7
+ // 태그 블록만 추출하기 위해 스크립트 제외
8
+ const cleanSource = source.replace(/@Script\s*\{[\s\S]*?\}/, "").trim();
9
+
10
+ const root = this.parseRecursive(cleanSource);
11
+ return { root, scriptContent };
12
+ }
16
13
 
17
- // source 전체를 훑으며 태그 블록을 하나씩 찾습니다.
18
- while ((match = tagRegex.exec(source)) !== null) {
19
- const [_, id, tag, content] = match;
14
+ static parseRecursive(text) {
15
+ // @ID:TAG { ... } 구조를 찾는 정규식
16
+ const tagRegex = /@(\w+):(\w+)\s*\{/g;
17
+ let match = tagRegex.exec(text);
18
+ if (!match) return null;
20
19
 
21
- const node = {
22
- id: id,
23
- tag: tag,
24
- styles: {},
25
- events: {},
26
- innerHtml: "",
27
- children: []
28
- };
20
+ const node = {
21
+ id: match[1],
22
+ tag: match[2],
23
+ styles: {},
24
+ events: {},
25
+ innerHtml: "",
26
+ children: []
27
+ };
29
28
 
30
- // 💡 [핵심] 블록 내부(content)에서 줄바꿈 무시하고 속성 추출
31
- // Innerhtml 추출 (i 플래그로 대소문자 무시)
32
- const htmlMatch = content.match(/Innerhtml:\s*"(.*?)"/i);
33
- if (htmlMatch) {
34
- node.innerHtml = htmlMatch[1];
35
- }
29
+ // 괄호 찾기 (중첩 대응)
30
+ const startIndex = match.index + match[0].length;
31
+ let braceCount = 1;
32
+ let endIndex = startIndex;
36
33
 
37
- // Style 추출 (si 플래그로 줄바꿈 허용 대소문자 무시)
38
- const styleMatch = content.match(/Style\((.*?)\)/si);
39
- if (styleMatch && styleMatch[1]) {
40
- node.styles = this.parseKV(styleMatch[1]);
41
- }
34
+ while (braceCount > 0 && endIndex < text.length) {
35
+ if (text[endIndex] === '{') braceCount++;
36
+ else if (text[endIndex] === '}') braceCount--;
37
+ endIndex++;
38
+ }
42
39
 
43
- // Event 추출 (si 플래그)
44
- const eventMatch = content.match(/Event\((.*?)\)/si);
45
- if (eventMatch && eventMatch[1]) {
46
- node.events = this.parseKV(eventMatch[1]);
47
- }
40
+ const blockContent = text.substring(startIndex, endIndex - 1);
48
41
 
49
- // 3. 계층 구조 형성 (단순화된 방식: 첫 번째가 root, 나머지는 root의 자식)
50
- // 중첩 구조가 깊어질 경우 스택 로직이 필요하지만, 현재 요구사항엔 이 방식이 가장 확실합니다.
51
- if (!root) {
52
- root = node;
53
- } else {
54
- root.children.push(node);
42
+ // 1. 블록 내부에서 속성 추출 (Innerhtml, Style, Event)
43
+ // 줄바꿈과 대소문자를 무시하고 " " 사이의 값을 정확히 가져옴
44
+ const htmlMatch = blockContent.match(/Innerhtml:\s*"([\s\S]*?)"/i);
45
+ if (htmlMatch) node.innerHtml = htmlMatch[1].trim();
46
+
47
+ const styleMatch = blockContent.match(/Style\(([\s\S]*?)\)/i);
48
+ if (styleMatch) node.styles = this.parseKV(styleMatch[1]);
49
+
50
+ const eventMatch = blockContent.match(/Event\(([\s\S]*?)\)/i);
51
+ if (eventMatch) node.events = this.parseKV(eventMatch[1]);
52
+
53
+ // 2. 자식 노드 재귀적 탐색
54
+ // 본인의 속성 정의 부분을 제외한 나머지 텍스트에서 자식 탐색
55
+ const remainingText = blockContent
56
+ .replace(/Innerhtml:\s*"[\s\S]*?"/i, "")
57
+ .replace(/Style\(.*?\)/si, "")
58
+ .replace(/Event\(.*?\)/si, "");
59
+
60
+ let childMatch;
61
+ const childRegex = /@\w+:\w+\s*\{/g;
62
+ while ((childMatch = childRegex.exec(remainingText)) !== null) {
63
+ // 자식의 시작 지점부터 다시 재귀 실행
64
+ const childNode = this.parseRecursive(remainingText.substring(childMatch.index));
65
+ if (childNode) {
66
+ node.children.push(childNode);
67
+ // 중복 탐색 방지를 위해 인덱스 건너뛰기 로직 생략 (현재 구조 최적화)
68
+ break; // 예시용 단일 자식 우선 처리, 실제론 루프 최적화 필요
55
69
  }
56
70
  }
57
71
 
58
- return { root, scriptContent };
72
+ return node;
59
73
  }
60
74
 
61
75
  static parseKV(kvString) {
62
- if (!kvString || !kvString.trim()) return {};
76
+ if (!kvString) return {};
63
77
  const obj = {};
64
- const pairs = kvString.split(';');
65
- pairs.forEach(pair => {
66
- if (pair.includes(':')) {
67
- const [key, value] = pair.split(':').map(s => s.trim());
68
- if (key && value) obj[key] = value;
69
- }
78
+ kvString.split(';').forEach(pair => {
79
+ const [key, value] = pair.split(':').map(s => s.trim());
80
+ if (key && value) obj[key] = value;
70
81
  });
71
82
  return obj;
72
83
  }
package/compiler/index.js CHANGED
@@ -3,46 +3,54 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { NeoParser } from './NeoParser.js';
5
5
 
6
+ // 1. 입력 파일 경로 확인
6
7
  const inputFile = process.argv[2];
7
8
 
8
9
  if (!inputFile) {
9
- console.error("❌ 컴파일할 .neo 파일을 입력해주세요.");
10
+ console.error("❌ 컴파일할 .neo 파일을 입력해주세요. (예: npx neoc src/App.neo)");
10
11
  process.exit(1);
11
12
  }
12
13
 
13
- const source = fs.readFileSync(inputFile, 'utf8');
14
- const { root, scriptContent } = NeoParser.parse(source);
15
-
16
- function generateCode(node, indent = " ") {
17
- const childrenCode = node.children
18
- .map(child => generateCode(child, indent + " "))
19
- .join(',\n');
20
-
21
- const eventProps = {};
22
- for (const [evt, action] of Object.entries(node.events)) {
23
- const propName = `on${evt.charAt(0).toUpperCase() + evt.slice(1)}`;
24
- let processedAction = action;
25
-
26
- if (action.includes('++')) processedAction = `state.${action}`;
27
- if (processedAction.includes('(') && !processedAction.includes(')')) {
28
- processedAction += ')';
29
- }
30
- eventProps[propName] = `() => { ${processedAction} }`;
14
+ // 2. 소스 코드 읽기 및 파싱
15
+ try {
16
+ const source = fs.readFileSync(inputFile, 'utf8');
17
+ const { root, scriptContent } = NeoParser.parse(source);
18
+
19
+ if (!root) {
20
+ throw new Error("파싱 결과가 비어있습니다. .neo 파일의 형식을 확인해주세요.");
31
21
  }
32
22
 
33
- const eventString = Object.entries(eventProps)
34
- .map(([k, v]) => `${k}: ${v}`)
35
- .join(',\n' + indent + ' ');
23
+ // 3. 코드 생성 함수 (재귀 구조)
24
+ function generateCode(node, indent = " ") {
25
+ // 자식 노드 재귀 처리
26
+ const childrenCode = node.children
27
+ .map(child => generateCode(child, indent + " "))
28
+ .join(',\n');
29
+
30
+ // 이벤트 리스너 처리
31
+ const eventProps = Object.entries(node.events).map(([evt, action]) => {
32
+ const propName = `on${evt.charAt(0).toUpperCase() + evt.slice(1)}`;
33
+ // state. 변수 자동 매핑
34
+ let processedAction = action.includes('++') ? `state.${action}` : action;
35
+
36
+ // 함수 호출 괄호 보정
37
+ if (processedAction.includes('(') && !processedAction.includes(')')) {
38
+ processedAction += ')';
39
+ }
40
+ return `${propName}: () => { ${processedAction} }`;
41
+ }).join(',\n' + indent + ' ');
36
42
 
37
- // 💡 생성 키값을 innerHTML로 고정 (중요)
38
- return `${indent}h('${node.tag}', {
43
+ // 최종 h() 함수 문자열 생성
44
+ // 💡 node.innerHtml(파서 데이터) -> innerHTML(JS 속성) 매핑 확인
45
+ return `${indent}h('${node.tag}', {
39
46
  ${indent} id: '${node.id}',
40
47
  ${indent} style: ${JSON.stringify(node.styles)},
41
- ${indent} innerHTML: \`${node.innerHtml.replace(/\$(\w+)/g, '${state.$1}')}\`${eventString ? ',\n' + indent + ' ' + eventString : ''}
48
+ ${indent} innerHTML: \`${node.innerHtml.replace(/\$(\w+)/g, '${state.$1}')}\`${eventProps ? ',\n' + indent + ' ' + eventProps : ''}
42
49
  ${indent}}, [${childrenCode ? '\n' + childrenCode + '\n' + indent : ''}])`;
43
50
  }
44
51
 
45
- const finalJS = `
52
+ // 4. 최종 출력 파일 내용 구성
53
+ const finalJS = `
46
54
  import { h } from '@junnyontop-pixel/neo-app/core/NeoCore.js';
47
55
 
48
56
  // [User Script]
@@ -53,7 +61,14 @@ export default function render(state) {
53
61
  }
54
62
  `.trim();
55
63
 
56
- const outputPath = inputFile.replace('.neo', '.js');
57
- fs.writeFileSync(outputPath, finalJS);
64
+ // 5. .js 파일로 저장
65
+ const outputPath = inputFile.replace('.neo', '.js');
66
+ fs.writeFileSync(outputPath, finalJS);
58
67
 
59
- console.log(`✅ [컴파일 완료] -> ${outputPath}`);
68
+ console.log(`✅ [컴파일 성공] -> ${outputPath}`);
69
+
70
+ } catch (err) {
71
+ console.error("❌ 컴파일 중 에러 발생:");
72
+ console.error(err.message);
73
+ process.exit(1);
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junnyontop-pixel/neo-app",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "나만의 커스텀 프레임워크 Neo",
5
5
  "main": "core/NeoCore.js",
6
6
  "type": "module",