@junnyontop-pixel/neo-app 1.1.9 → 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.
@@ -2,73 +2,82 @@ export class NeoParser {
2
2
  static parse(source) {
3
3
  let scriptContent = "";
4
4
  const scriptMatch = source.match(/@Script\s*\{([\s\S]*?)\}/);
5
- if (scriptMatch) {
6
- scriptContent = scriptMatch[1].trim();
7
- }
5
+ if (scriptMatch) scriptContent = scriptMatch[1].trim();
8
6
 
9
- // 태그 블록 분리 로직 강화
10
- const tokens = source.split(/(@\w+:\w+\s*\{)/).filter(t => t.trim());
11
- let root = null;
12
- 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
+ }
13
13
 
14
- tokens.forEach(token => {
15
- const tagMatch = token.match(/@(\w+):(\w+)\s*\{/);
16
-
17
- if (tagMatch) {
18
- const node = {
19
- id: tagMatch[1],
20
- tag: tagMatch[2],
21
- styles: {},
22
- events: {},
23
- innerHtml: "", // 파서 내부 저장용
24
- children: []
25
- };
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;
26
19
 
27
- // 대소문자 무시(i) 및 줄바꿈 허용(s) 플래그 적용
28
- const styleMatch = token.match(/Style\((.*?)\)/si);
29
- if (styleMatch && styleMatch[1]) {
30
- node.styles = this.parseKV(styleMatch[1]);
31
- }
20
+ const node = {
21
+ id: match[1],
22
+ tag: match[2],
23
+ styles: {},
24
+ events: {},
25
+ innerHtml: "",
26
+ children: []
27
+ };
32
28
 
33
- const eventMatch = token.match(/Event\((.*?)\)/si);
34
- if (eventMatch && eventMatch[1]) {
35
- node.events = this.parseKV(eventMatch[1]);
36
- }
29
+ // 괄호 찾기 (중첩 대응)
30
+ const startIndex = match.index + match[0].length;
31
+ let braceCount = 1;
32
+ let endIndex = startIndex;
37
33
 
38
- // Innerhtml, innerHTML, innerhtml 모두 대응
39
- const htmlMatch = token.match(/innerHTML:\s*"(.*?)"/i);
40
- if (htmlMatch) {
41
- node.innerHtml = htmlMatch[1];
42
- }
34
+ while (braceCount > 0 && endIndex < text.length) {
35
+ if (text[endIndex] === '{') braceCount++;
36
+ else if (text[endIndex] === '}') braceCount--;
37
+ endIndex++;
38
+ }
43
39
 
44
- if (!root) {
45
- root = node;
46
- } else if (stack.length > 0) {
47
- stack[stack.length - 1].children.push(node);
48
- }
49
- stack.push(node);
50
- }
40
+ const blockContent = text.substring(startIndex, endIndex - 1);
41
+
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]);
51
49
 
52
- if (token.includes('}')) {
53
- const closeCount = (token.match(/\}/g) || []).length;
54
- for (let i = 0; i < closeCount; i++) {
55
- if (stack.length > 0) stack.pop();
56
- }
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; // 예시용 단일 자식 우선 처리, 실제론 루프 최적화 필요
57
69
  }
58
- });
70
+ }
59
71
 
60
- return { root, scriptContent };
72
+ return node;
61
73
  }
62
74
 
63
75
  static parseKV(kvString) {
64
- if (!kvString || !kvString.trim()) return {};
76
+ if (!kvString) return {};
65
77
  const obj = {};
66
- const pairs = kvString.split(';');
67
- pairs.forEach(pair => {
68
- if (pair.includes(':')) {
69
- const [key, value] = pair.split(':').map(s => s.trim());
70
- if (key && value) obj[key] = value;
71
- }
78
+ kvString.split(';').forEach(pair => {
79
+ const [key, value] = pair.split(':').map(s => s.trim());
80
+ if (key && value) obj[key] = value;
72
81
  });
73
82
  return obj;
74
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.9",
3
+ "version": "1.1.11",
4
4
  "description": "나만의 커스텀 프레임워크 Neo",
5
5
  "main": "core/NeoCore.js",
6
6
  "type": "module",
package/src/App.js CHANGED
@@ -8,6 +8,7 @@ export default function render(state) {
8
8
  return h('button', {
9
9
  id: 'Btn',
10
10
  style: {},
11
- innerHTML: ``
11
+ innerHTML: `인사하기`,
12
+ onClick: () => { sayHello() }
12
13
  }, []);
13
14
  }