@scaffscript/core 0.2.3 → 0.2.6

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.
Files changed (2) hide show
  1. package/dist/index.cjs +632 -472
  2. package/package.json +41 -41
package/dist/index.cjs CHANGED
@@ -314,7 +314,7 @@ async function getScaffConfig() {
314
314
  };
315
315
  }
316
316
  // src/parser/regex.ts
317
- var commentRegex = /\/\/[^\n]*|\/\*(?!\*)[\s\S]*?\*\//g;
317
+ var commentRegex = /(?<!\/)\/\/(?!\/)[^\n]*|\/\*(?!\*)[\s\S]*?\*\//g;
318
318
  var implRegex = /impl\s+(?<name>[\w+]+)\s+\{\s+(?<body>[.\s\S]+?)\}/g;
319
319
  var implHeaderRegex = /impl\s+(?<name>\w+)\s*\{/g;
320
320
  var fnParamsRegex = /\((?<params>[^)]*)\)/g;
@@ -344,14 +344,14 @@ function getTabLevels(str, tabType) {
344
344
  // package.json
345
345
  var package_default = {
346
346
  name: "@scaffscript/core",
347
- version: "0.2.1",
347
+ version: "0.2.6",
348
348
  repository: {
349
349
  type: "git",
350
350
  url: "https://github.com/undervolta/scaffscript"
351
351
  },
352
352
  main: "dist/index.cjs",
353
353
  devDependencies: {
354
- "@types/bun": "latest"
354
+ "@types/bun": "^1.3.11"
355
355
  },
356
356
  peerDependencies: {
357
357
  typescript: "^5"
@@ -516,6 +516,362 @@ async function readAndSplitFiles(files, config) {
516
516
  }
517
517
  return res;
518
518
  }
519
+ // src/parser/import-module.ts
520
+ function countTabsBeforeSubstring(str, sub, tabChar) {
521
+ const idx = str.indexOf(sub);
522
+ if (idx === -1)
523
+ return -1;
524
+ const lineStart = str.lastIndexOf(`
525
+ `, idx - 1) + 1;
526
+ const segment = str.slice(lineStart, idx);
527
+ let count = 0;
528
+ for (const ch of segment) {
529
+ if (ch === tabChar)
530
+ count++;
531
+ else
532
+ break;
533
+ }
534
+ return count;
535
+ }
536
+ function resolveImportPath(filePath, importPath, config) {
537
+ if (!config.path)
538
+ return resolvePath(`${filePath}/${importPath}`);
539
+ const useWildcard = Object.keys(config.path).filter((k) => k.endsWith("*")).find((k) => importPath.startsWith(k.slice(0, -1)));
540
+ if (useWildcard) {
541
+ const pathAlias = config.path[useWildcard]?.replace("*", "");
542
+ const dynPath = importPath.replace(useWildcard.slice(0, -2), "");
543
+ if (!pathAlias) {
544
+ log.error(`Path \x1B[33m${importPath}\x1B[0m not found in path aliases. Aborting...`);
545
+ return "";
546
+ }
547
+ if (pathAlias.startsWith("~")) {
548
+ return resolvePath(`${config.source}/${pathAlias.replace("~/", "").replace("~", "")}${dynPath}`);
549
+ }
550
+ return resolvePath(`${filePath}/${pathAlias}${dynPath}`);
551
+ } else if (config.path[importPath]) {
552
+ const pathAlias = config.path[importPath];
553
+ if (pathAlias.startsWith("~")) {
554
+ return resolvePath(`${config.source}/${pathAlias.replace("~/", "").replace("~", "")}`);
555
+ }
556
+ return resolvePath(`${filePath}/${pathAlias}`);
557
+ }
558
+ return resolvePath(`${filePath}/${importPath}`);
559
+ }
560
+ async function getModuleUsage(module2, fileGroup, file, config) {
561
+ const matches = [...file.content.matchAll(modControlRegex)].filter((match) => match.groups.cmd !== "export");
562
+ const limit = 10;
563
+ const results = [];
564
+ let isInvalid = false;
565
+ for (let i = 0;i < matches.length; i += limit) {
566
+ const batch = matches.slice(i, i + limit);
567
+ const res = await Promise.all(batch.map(async (match) => {
568
+ const { cmd, mod, path: path3 } = match.groups;
569
+ const res2 = {
570
+ cmd: null,
571
+ files: null,
572
+ modList: null,
573
+ targetPath: null,
574
+ targetStr: ""
575
+ };
576
+ if (!(cmd && mod && path3)) {
577
+ log.error(`Invalid module control statement: \x1B[34m${cmd} ${mod} from ${path3}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
578
+ isInvalid = true;
579
+ return null;
580
+ }
581
+ const fromPath = normalizePath(resolveImportPath(file.path, path3.slice(1, -1), config));
582
+ const modList = {};
583
+ const alias = {};
584
+ if (!module2[fromPath]) {
585
+ if (cmd === "include" && mod.startsWith("{") && (mod.includes('"') || mod.includes("'"))) {
586
+ const files = mod.slice(1, -1).split(",").map((m) => m.trim());
587
+ const filePaths = [];
588
+ for (const f of files) {
589
+ const filePath = normalizePath(resolvePath(`${file.path}/${f.slice(1, -1)}`));
590
+ const targetFile = fileGroup.normal.find((fl) => {
591
+ let targetFile2 = `${fl.path}/${fl.name}`;
592
+ let currFile = filePath;
593
+ if (!targetFile2.endsWith(".gml"))
594
+ targetFile2 += ".gml";
595
+ if (!currFile.endsWith(".gml"))
596
+ currFile += ".gml";
597
+ return targetFile2 === currFile;
598
+ });
599
+ if (targetFile)
600
+ filePaths.push(targetFile);
601
+ else if (await fileExists(filePath))
602
+ filePaths.push(filePath);
603
+ else {
604
+ if (config.onNotFound === "error") {
605
+ log.error(`File \x1B[33m${f.slice(1, -1)}\x1B[0m from \x1B[32m${file.path}\x1B[0m not found. Aborting...`);
606
+ isInvalid = true;
607
+ return null;
608
+ } else
609
+ log.warn(`File \x1B[33m${f.slice(1, -1)}\x1B[0m from \x1B[32m${file.path}\x1B[0m not found. Skipping this file...`);
610
+ }
611
+ }
612
+ return {
613
+ cmd,
614
+ files: filePaths,
615
+ modList,
616
+ targetPath: fromPath,
617
+ targetStr: match[0]
618
+ };
619
+ }
620
+ log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
621
+ isInvalid = true;
622
+ return null;
623
+ }
624
+ if (mod === "*" || mod.startsWith("{") && mod.endsWith("}")) {
625
+ const targetMods = mod === "*" ? null : mod.slice(1, -1).split(",").map((m) => {
626
+ const split = m.split(":");
627
+ const key = split[0].trim();
628
+ if (split.length === 1)
629
+ alias[key] = key;
630
+ else
631
+ alias[key] = split[1].trim();
632
+ if (!module2[fromPath]) {
633
+ if (config.onNotFound === "error") {
634
+ log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
635
+ isInvalid = true;
636
+ } else
637
+ log.warn(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Skipping this module...`);
638
+ return null;
639
+ }
640
+ if (!module2[fromPath][key]) {
641
+ if (config.onNotFound === "error") {
642
+ log.error(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
643
+ isInvalid = true;
644
+ } else
645
+ log.warn(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
646
+ return null;
647
+ }
648
+ return key;
649
+ }).filter(Boolean);
650
+ Object.entries(module2[fromPath]).forEach(([key, value]) => {
651
+ if (mod === "*")
652
+ modList[key] = { name: key, as: key, value, usingAlias: false };
653
+ else if (targetMods.includes(key)) {
654
+ const usingAlias = key in alias && alias[key] !== key;
655
+ modList[alias[key] ?? key] = {
656
+ name: key,
657
+ as: alias[key] ?? key,
658
+ value,
659
+ usingAlias
660
+ };
661
+ if (usingAlias) {
662
+ if (!module2[fromPath])
663
+ module2[fromPath] = {};
664
+ module2[fromPath][`@${alias[key]}`] = value;
665
+ }
666
+ }
667
+ });
668
+ } else {
669
+ if (!module2[fromPath][mod]) {
670
+ if (config.onNotFound === "error") {
671
+ log.error(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
672
+ isInvalid = true;
673
+ } else
674
+ log.warn(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
675
+ return res2;
676
+ }
677
+ modList[mod] = {
678
+ name: mod,
679
+ as: mod,
680
+ value: module2[fromPath][mod],
681
+ usingAlias: false
682
+ };
683
+ }
684
+ return {
685
+ cmd,
686
+ files: null,
687
+ modList,
688
+ targetPath: fromPath,
689
+ targetStr: match[0]
690
+ };
691
+ }));
692
+ if (isInvalid)
693
+ break;
694
+ results.push(...res);
695
+ }
696
+ return !isInvalid && results.every((r) => r) ? results : null;
697
+ }
698
+ async function implementModules(module2, fileGroup, file, config, mods) {
699
+ if (!mods)
700
+ mods = await getModuleUsage(module2, fileGroup, file, config);
701
+ if (!mods)
702
+ return null;
703
+ const toRemove = [...file.content.matchAll(modControlRegex)];
704
+ for (const rm of toRemove) {
705
+ if (rm.groups?.cmd !== "include")
706
+ file.content = file.content.replace(rm[0], "");
707
+ }
708
+ for (const mod of mods) {
709
+ if (!mod)
710
+ return null;
711
+ if (!mod.cmd)
712
+ return null;
713
+ switch (mod.cmd) {
714
+ case "include":
715
+ let toReplace = "";
716
+ if (!mod.files) {
717
+ if (!mod.modList)
718
+ return null;
719
+ const modEntries = Object.entries(mod.modList);
720
+ const modLen = modEntries.length;
721
+ const modIterator = modEntries.entries();
722
+ for (const [idx, [_, include]] of modIterator) {
723
+ if (idx > 0)
724
+ switch (include.value.type) {
725
+ case "function":
726
+ case "class":
727
+ case "type":
728
+ case "interface":
729
+ case "enum":
730
+ toReplace += `
731
+ `;
732
+ break;
733
+ }
734
+ if (include.value.type === "class")
735
+ toReplace += `${include.value.declaration} {
736
+ ${include.value.body}
737
+ }`;
738
+ else
739
+ toReplace += include.value.parsedStr + `
740
+ `;
741
+ if (idx === modLen - 1)
742
+ toReplace += `
743
+ `;
744
+ }
745
+ file.content = file.content.replace(mod.targetStr, toReplace);
746
+ } else {
747
+ for (const fileOrPath of mod.files) {
748
+ if (typeof fileOrPath === "string") {
749
+ const content = await fsRuntime.readText(fileOrPath);
750
+ toReplace += content + `
751
+
752
+ `;
753
+ } else
754
+ toReplace += fileOrPath.content + `
755
+
756
+ `;
757
+ }
758
+ file.content = file.content.replace(mod.targetStr, toReplace);
759
+ }
760
+ break;
761
+ case "import":
762
+ const cmdMatches = [...file.content.matchAll(contentModRegex)];
763
+ const shortCmdMatches = [
764
+ ...file.content.matchAll(contentModShortRegex)
765
+ ];
766
+ const useMatches = [...file.content.matchAll(useModRegex)];
767
+ for (const match of cmdMatches) {
768
+ const { cmd: contentCmd, mod: contentMod } = match.groups;
769
+ if (!contentCmd || !contentMod) {
770
+ log.error(`Invalid content module statement: \x1B[34m${contentCmd} ${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
771
+ return null;
772
+ }
773
+ if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
774
+ continue;
775
+ if (config.debugLevel >= 1)
776
+ log.debug(`Content module statement found: \x1B[34m${contentCmd} ${contentMod}\x1B[0m in \x1B[33m${file.name === "" ? "index" : file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
777
+ let parsedStr = "";
778
+ switch (contentCmd) {
779
+ case "content":
780
+ const tabChar = config.tabType === "1t" ? "\t" : config.tabType === "2s" ? " " : " ";
781
+ const tabCnt = countTabsBeforeSubstring(file.content, match[0], tabChar);
782
+ const usedMod = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod];
783
+ parsedStr = usedMod?.type !== "class" ? usedMod?.parsedStr ?? "" : `${usedMod?.declaration} {
784
+ ${usedMod?.body}
785
+ }`;
786
+ if (tabCnt > 0) {
787
+ const tabLevels = getTabLevels(parsedStr, config.tabType).map((l) => l + tabCnt);
788
+ file.content = file.content.replace(match[0], parsedStr.split(`
789
+ `).map((l, i) => tabLevels[i] ? insertTabs(tabLevels[i], config.tabType) + l : l).join(`
790
+ `));
791
+ } else
792
+ file.content = file.content.replace(match[0], parsedStr);
793
+ break;
794
+ case "nameof":
795
+ parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].name;
796
+ file.content = file.content.replace(`@nameof ${contentMod}`, parsedStr);
797
+ break;
798
+ case "typeof":
799
+ parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].type;
800
+ file.content = file.content.replace(`@typeof ${contentMod}`, `"${parsedStr}"`);
801
+ break;
802
+ case "valueof":
803
+ parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].value;
804
+ file.content = file.content.replace(`@valueof ${contentMod}`, parsedStr);
805
+ break;
806
+ }
807
+ }
808
+ for (const match of shortCmdMatches) {
809
+ const { mod: contentMod } = match.groups;
810
+ if (!contentMod) {
811
+ log.error(`Invalid content module statement: \x1B[34m@:${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
812
+ return null;
813
+ }
814
+ if (config.debugLevel >= 1)
815
+ log.debug(`Valueof module statement found: \x1B[34m${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
816
+ if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
817
+ continue;
818
+ const parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].value;
819
+ file.content = file.content.replace(`@:${contentMod}`, parsedStr);
820
+ }
821
+ for (const match of useMatches) {
822
+ const { mod: contentMod, body } = match.groups;
823
+ if (!contentMod || !body) {
824
+ log.error(`Invalid content module statement: \x1B[34m@use ${contentMod} ${body}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
825
+ return null;
826
+ }
827
+ if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
828
+ continue;
829
+ if (config.debugLevel >= 1)
830
+ log.debug(`Use module statement found: \x1B[34m@use ${contentMod} ${body}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
831
+ const lines = body.slice(1, -1).split(`
832
+ `).filter(Boolean);
833
+ const pairs = lines.map((l) => l.split(":").map((p) => {
834
+ const delimIdx = p.lastIndexOf(",");
835
+ return p.slice(0, delimIdx === -1 ? undefined : delimIdx).trim();
836
+ }));
837
+ const tabChar = config.tabType === "1t" ? "\t" : config.tabType === "2s" ? " " : " ";
838
+ const tabCnt = countTabsBeforeSubstring(file.content.slice(file.content.lastIndexOf(`
839
+ `, match.index), match.index + match[0].length), "@", tabChar);
840
+ const tabLevels = getTabLevels(body, config.tabType).map((l) => l + tabCnt);
841
+ const currMod = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod];
842
+ const modMember = Object.entries(currMod.member);
843
+ const tabLevel = tabCnt + tabLevels[Math.min(1, tabLevels.length - 1)];
844
+ let res = `{
845
+ `;
846
+ for (const [idx, [mName, mVal]] of modMember.entries()) {
847
+ const pairIdx = pairs.findIndex((p) => p[0] === mName);
848
+ const pair = pairIdx > -1 ? pairs[pairIdx] : null;
849
+ const value = pair ? pair.length === 2 ? pair[1] : mVal.value : mVal.value;
850
+ if (value) {
851
+ res += `${insertTabs(tabLevel, config.tabType)}${mName}: ${value}${idx < modMember.length - 2 ? "," : ""}
852
+ `;
853
+ }
854
+ if (pairIdx > -1)
855
+ swapAndPop(pairs, pairIdx);
856
+ }
857
+ for (const [idx, pair] of pairs.entries()) {
858
+ if (idx === 0)
859
+ res = res.slice(0, -1) + `,
860
+ `;
861
+ res += `${insertTabs(tabLevel, config.tabType)}${pair[0]}: ${pair[1]}${idx < pairs.length - 1 ? "," : ""}
862
+ `;
863
+ }
864
+ res += insertTabs(tabCnt, config.tabType) + "}";
865
+ file.content = file.content.replace(match[0], res);
866
+ }
867
+ break;
868
+ }
869
+ file.content = file.content.trim() + `
870
+ `;
871
+ }
872
+ return mods;
873
+ }
874
+
519
875
  // src/parser/export-module.ts
520
876
  function inferType(value) {
521
877
  value = value.trim();
@@ -637,6 +993,7 @@ function countBraces(line) {
637
993
  return count;
638
994
  }
639
995
  function parseFnParams(str) {
996
+ fnParamsRegex.lastIndex = 0;
640
997
  const match = str.match(fnParamsRegex);
641
998
  let names = [];
642
999
  let defaults = [];
@@ -657,9 +1014,10 @@ function parseFnParams(str) {
657
1014
  return { names, defaults, combined };
658
1015
  }
659
1016
  function convertClassMethods(classBody) {
1017
+ const reservedMethodNames = new Set(["if", "for", "while", "switch", "catch", "do", "else", "try", "finally"]);
660
1018
  const methodRegex = /(\w+)\s*\(([^)]*)\)\s*{([\s\S]*?)}/g;
661
1019
  return classBody.replace(methodRegex, (match, methodName, params, body) => {
662
- if (methodName === "function")
1020
+ if (methodName === "function" || reservedMethodNames.has(methodName))
663
1021
  return match;
664
1022
  return `${methodName} = function(${params.replaceAll("?", " = undefined")}) {${body}}`;
665
1023
  });
@@ -718,7 +1076,14 @@ function getExportedModules(files, config) {
718
1076
  const body = funcCode.slice(funcCode.indexOf("{")).trim();
719
1077
  const params = parseFnParams(funcCode);
720
1078
  const parsedStr = `function ${name}(${params.combined.join(", ")}) ${body}`;
721
- module2[filePath][name] = { name, value: body.slice(1, -1), type: "function", parsedStr };
1079
+ module2[filePath][name] = {
1080
+ name,
1081
+ value: body.slice(1, -1),
1082
+ type: "function",
1083
+ parsedStr,
1084
+ header: funcCode.slice(0, funcCode.indexOf("{")).trim(),
1085
+ blockValue: insertTabs(1, config.tabType) + body.slice(1, -1)
1086
+ };
722
1087
  }
723
1088
  } else if (line.startsWith("export class ")) {
724
1089
  const classLines = [];
@@ -754,7 +1119,14 @@ function getExportedModules(files, config) {
754
1119
  const parsedStr = `function ${name}(${constructor.replaceAll("?", " = undefined")}) constructor {
755
1120
  ${insertTabs(1, config.tabType)}${classBody}
756
1121
  }`;
757
- module2[filePath][name] = { name, value: classCode.replace("export ", ""), type: "class", parsedStr };
1122
+ module2[filePath][name] = {
1123
+ name,
1124
+ value: classCode.replace("export ", ""),
1125
+ type: "class",
1126
+ parsedStr,
1127
+ declaration: `function ${name}(${constructor.replaceAll("?", " = undefined")}) constructor`,
1128
+ body: `${insertTabs(1, config.tabType)}${classBody}`
1129
+ };
758
1130
  }
759
1131
  } else if (line.startsWith("export interface ")) {
760
1132
  const interfaceLines = [];
@@ -857,14 +1229,28 @@ ${insertTabs(1, config.tabType)}${classBody}
857
1229
  const parsedStr = `${name} = function(${params.combined.join(", ")}) ${arrowBlock}`;
858
1230
  if (!module2[filePath])
859
1231
  module2[filePath] = {};
860
- module2[filePath][name] = { name, value: arrowBlock.slice(1, -1), type: "arrow-fn", parsedStr };
1232
+ module2[filePath][name] = {
1233
+ name,
1234
+ value: arrowBlock.slice(1, -1),
1235
+ type: "function",
1236
+ header: `${name} = function(${params.combined.join(", ")})`,
1237
+ blockValue: arrowBlock,
1238
+ parsedStr
1239
+ };
861
1240
  } else {
862
1241
  const params = parseFnParams(valuePart);
863
1242
  const body = valuePart.split("=>")[1].trim();
864
1243
  const parsedStr = `${name} = function(${params.combined.join(", ")}) { return ${body}; }`;
865
1244
  if (!module2[filePath])
866
1245
  module2[filePath] = {};
867
- module2[filePath][name] = { name, value: body, type: "arrow-fn", parsedStr };
1246
+ module2[filePath][name] = {
1247
+ name,
1248
+ value: body,
1249
+ type: "function",
1250
+ header: `${name} = function(${params.combined.join(", ")})`,
1251
+ blockValue: body,
1252
+ parsedStr
1253
+ };
868
1254
  }
869
1255
  } else if (valuePart.startsWith("function")) {
870
1256
  if (valuePart.trim().endsWith("{")) {
@@ -886,12 +1272,27 @@ ${insertTabs(1, config.tabType)}${classBody}
886
1272
  const parsedStr = `${name} = function(${params.combined.join(", ")}) ${funcBlock}`;
887
1273
  if (!module2[filePath])
888
1274
  module2[filePath] = {};
889
- module2[filePath][name] = { name, value: funcBlock.slice(1, -1), type: "method", parsedStr };
1275
+ module2[filePath][name] = {
1276
+ name,
1277
+ value: funcBlock.slice(1, -1),
1278
+ type: "function",
1279
+ header: `${name} = function(${params.combined.join(", ")})`,
1280
+ blockValue: funcBlock,
1281
+ parsedStr
1282
+ };
890
1283
  } else {
1284
+ const params = parseFnParams(valuePart);
891
1285
  const parsedStr = `${name} = ${valuePart}`;
892
1286
  if (!module2[filePath])
893
1287
  module2[filePath] = {};
894
- module2[filePath][name] = { name, value: line.replace("export ", ""), type: "method", parsedStr };
1288
+ module2[filePath][name] = {
1289
+ name,
1290
+ value: line.replace("export ", ""),
1291
+ type: "function",
1292
+ header: `${name} = function(${params.combined.join(", ")})`,
1293
+ blockValue: valuePart,
1294
+ parsedStr
1295
+ };
895
1296
  }
896
1297
  } else {
897
1298
  let varType = "var";
@@ -901,7 +1302,6 @@ ${insertTabs(1, config.tabType)}${classBody}
901
1302
  varType = "let";
902
1303
  else if (decl?.includes("var"))
903
1304
  varType = "var";
904
- const noVarKeyword = varType === "var" && !parts[0].includes("var");
905
1305
  if (!module2[filePath])
906
1306
  module2[filePath] = {};
907
1307
  const parsedStr = varType !== "const" ? `${varType === "let" ? "" : "var "}${name} = ${valuePart};` : `#macro ${name} ${valuePart}`;
@@ -913,503 +1313,260 @@ ${insertTabs(1, config.tabType)}${classBody}
913
1313
  }
914
1314
  }
915
1315
  retryLen = retryList.length;
916
- while (retryCount < 10 && retryLastLen !== retryLen) {
917
- for (const retry of retryList) {
918
- if (!module2[retry.filePath])
919
- continue;
920
- if (!module2[retry.filePath][retry.targetName])
921
- continue;
922
- switch (module2[retry.filePath][retry.name].type) {
923
- case "interface":
924
- const currInterface = module2[retry.filePath][retry.name];
925
- const extendsInterface = module2[retry.filePath][retry.targetName];
926
- for (const [mName, m] of Object.entries(extendsInterface.member)) {
927
- currInterface.member[mName] = { type: m.type, value: m.value };
928
- }
929
- break;
930
- case "type":
931
- const currType = module2[retry.filePath][retry.name];
932
- const extendsType = module2[retry.filePath][retry.targetName];
933
- for (const [mName, m] of Object.entries(extendsType.member)) {
934
- currType.member[mName] = { type: m.type, value: m.value };
935
- }
936
- break;
937
- }
938
- }
939
- retryLastLen = retryLen;
940
- retryLen = retryList.length;
941
- retryCount++;
942
- }
943
- return module2;
944
- }
945
- // src/parser/class-implement.ts
946
- function convertArrowFn(str) {
947
- return str.replace(arrowFnHeaderRegex, (match, _p1, _p2, _offset, _input, groups) => {
948
- if (!groups?.name)
949
- return match;
950
- const rawParams = groups.params?.trim() ?? "";
951
- const paramsSource = rawParams.startsWith("(") ? rawParams : `(${rawParams})`;
952
- const params = parseFnParams(paramsSource);
953
- return `${groups.name} = function(${params.combined.join(", ")})`;
954
- });
955
- }
956
- function parseHeader(str, regex = implHeaderRegex) {
957
- const results = [];
958
- let match;
959
- while ((match = regex.exec(str)) !== null) {
960
- const name = match.groups.name;
961
- const start = regex.lastIndex;
962
- let braceCount = 1;
963
- let inString = false;
964
- let stringChar = "";
965
- let i = start;
966
- for (;i < str.length; i++) {
967
- const char = str[i];
968
- if (inString) {
969
- if (char === stringChar && (i === 0 || str[i - 1] !== "\\")) {
970
- inString = false;
971
- }
972
- } else {
973
- if (char === '"' || char === "'") {
974
- inString = true;
975
- stringChar = char;
976
- } else if (char === "{") {
977
- braceCount++;
978
- } else if (char === "}") {
979
- braceCount--;
980
- if (braceCount === 0)
981
- break;
982
- }
1316
+ while (retryCount < 10 && retryLastLen !== retryLen) {
1317
+ for (const retry of retryList) {
1318
+ if (!module2[retry.filePath])
1319
+ continue;
1320
+ if (!module2[retry.filePath][retry.targetName])
1321
+ continue;
1322
+ switch (module2[retry.filePath][retry.name].type) {
1323
+ case "interface":
1324
+ const currInterface = module2[retry.filePath][retry.name];
1325
+ const extendsInterface = module2[retry.filePath][retry.targetName];
1326
+ for (const [mName, m] of Object.entries(extendsInterface.member)) {
1327
+ currInterface.member[mName] = { type: m.type, value: m.value };
1328
+ }
1329
+ break;
1330
+ case "type":
1331
+ const currType = module2[retry.filePath][retry.name];
1332
+ const extendsType = module2[retry.filePath][retry.targetName];
1333
+ for (const [mName, m] of Object.entries(extendsType.member)) {
1334
+ currType.member[mName] = { type: m.type, value: m.value };
1335
+ }
1336
+ break;
983
1337
  }
984
1338
  }
985
- const body = str.slice(start, i);
986
- results.push({ name, body });
987
- regex.lastIndex = i + 1;
1339
+ retryLastLen = retryLen;
1340
+ retryLen = retryList.length;
1341
+ retryCount++;
988
1342
  }
989
- return results;
1343
+ return module2;
990
1344
  }
991
- function implementClass(module2, fileGroup, config) {
992
- if (fileGroup.generate.length == 0 && fileGroup.scaff.length == 0) {
993
- log.warn("No files to implement classes from.");
1345
+ function reexportModule(module2, file, config) {
1346
+ const reexportMatches = [...file.content.matchAll(modControlRegex)].filter((match) => match.groups?.cmd === "export");
1347
+ if (!reexportMatches.length)
994
1348
  return false;
995
- }
996
- const toImpl = [];
997
- for (const file of fileGroup.scaff) {
998
- if (file.childs.length > 0)
999
- file.childs.forEach((child) => toImpl.push({ parent: file, file: child }));
1000
- }
1001
- for (const file of fileGroup.generate) {
1002
- if (file.childs.length > 0)
1003
- file.childs.forEach((child) => toImpl.push({ parent: file, file: child }));
1004
- }
1005
- for (const fileImpl of toImpl) {
1006
- const filePath = fileImpl.parent.isIndex ? fileImpl.parent.path : `${fileImpl.parent.path}/${fileImpl.parent.name}`;
1007
- const match = parseHeader(fileImpl.file.content);
1008
- const classNames = [];
1009
- for (const [mIdx, m] of match.entries()) {
1010
- const { name: className } = m;
1011
- let { body } = m;
1012
- body = convertClassMethods(body);
1013
- body = convertArrowFn(body);
1014
- if (!className || !body)
1015
- continue;
1016
- classNames.push(className);
1017
- if (!module2[filePath] || !module2[filePath][className]) {
1018
- let newFilePath = null;
1019
- for (const file of fileGroup.scaff) {
1020
- if (file.content.includes(`class ${className} {`)) {
1021
- newFilePath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1022
- break;
1023
- }
1024
- }
1025
- if (!newFilePath) {
1026
- for (const file of fileGroup.generate) {
1027
- if (file.content.includes(`class ${className} {`)) {
1028
- newFilePath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1029
- break;
1030
- }
1031
- }
1349
+ for (const match of reexportMatches) {
1350
+ const { cmd, mod, path: path3 } = match.groups;
1351
+ if (!(cmd && mod && path3)) {
1352
+ log.error(`Invalid module control statement: \x1B[34m${cmd} ${mod} from ${path3}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
1353
+ return false;
1354
+ }
1355
+ const fromPath = normalizePath(resolveImportPath(file.path, path3.slice(1, -1), config));
1356
+ const thisPath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1357
+ const alias = {};
1358
+ if (mod === "*" || mod.startsWith("{") && mod.endsWith("}")) {
1359
+ const targetMods = mod === "*" ? null : mod.slice(1, -1).split(",").map((m) => {
1360
+ const split = m.split(":");
1361
+ const key = split[0].trim();
1362
+ if (split.length === 1)
1363
+ alias[key] = key;
1364
+ else
1365
+ alias[key] = split[1].trim();
1366
+ if (!module2[fromPath]) {
1367
+ if (config.onNotFound === "error") {
1368
+ log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
1369
+ return false;
1370
+ } else
1371
+ log.warn(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Skipping this module...`);
1372
+ return false;
1032
1373
  }
1033
- if (!newFilePath) {
1374
+ if (!module2[fromPath][key]) {
1034
1375
  if (config.onNotFound === "error") {
1035
- log.error(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Aborting...`);
1376
+ log.error(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
1036
1377
  return false;
1037
- } else {
1038
- log.warn(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Skipping this class...`);
1039
- continue;
1040
- }
1378
+ } else
1379
+ log.warn(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
1380
+ return false;
1041
1381
  }
1042
- const classCloseBracket = module2[newFilePath][className].parsedStr.lastIndexOf("}");
1043
- module2[newFilePath][className].parsedStr = module2[newFilePath][className].parsedStr.slice(0, classCloseBracket) + `${body.replace(`
1044
- `, "")}` + (mIdx < match.length - 1 ? `
1045
-
1046
- ` : `
1047
- }
1048
- `);
1049
- if (mIdx === match.length - 1) {
1050
- for (const name of classNames) {
1051
- if (!module2[filePath] || !module2[filePath][name])
1052
- continue;
1053
- module2[filePath][name].parsedStr += `}
1054
- `;
1055
- }
1382
+ return key;
1383
+ }).filter(Boolean);
1384
+ if (!module2[fromPath]) {
1385
+ if (config.onNotFound === "error") {
1386
+ log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
1387
+ return false;
1056
1388
  } else
1057
- classNames.splice(classNames.indexOf(className), 1);
1058
- continue;
1059
- } else {
1060
- const classCloseBracket = module2[filePath][className].parsedStr.lastIndexOf("}");
1061
- module2[filePath][className].parsedStr = module2[filePath][className].parsedStr.slice(0, classCloseBracket) + `${body.replace(`
1062
- `, "")}` + (mIdx < match.length - 1 ? `
1063
-
1064
- ` : `
1065
- }
1066
- `);
1389
+ log.warn(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Skipping this module...`);
1390
+ return false;
1391
+ }
1392
+ Object.entries(module2[fromPath]).forEach(([key, value]) => {
1393
+ if (!module2[thisPath])
1394
+ module2[thisPath] = {};
1395
+ if (mod === "*")
1396
+ module2[thisPath][key] = value;
1397
+ else if (targetMods.includes(key)) {
1398
+ const usingAlias = key in alias && alias[key] !== key;
1399
+ if (usingAlias)
1400
+ module2[thisPath][`@${alias[key]}`] = value;
1401
+ else
1402
+ module2[thisPath][key] = value;
1403
+ }
1404
+ });
1405
+ } else {
1406
+ if (!module2[fromPath]) {
1407
+ if (config.onNotFound === "error") {
1408
+ log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
1409
+ return false;
1410
+ } else
1411
+ log.warn(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Skipping this module...`);
1412
+ return false;
1413
+ }
1414
+ if (!module2[fromPath][mod]) {
1415
+ if (config.onNotFound === "error") {
1416
+ log.error(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
1417
+ return false;
1418
+ } else
1419
+ log.warn(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
1420
+ return false;
1067
1421
  }
1422
+ if (!module2[thisPath])
1423
+ module2[thisPath] = {};
1424
+ module2[thisPath][mod] = module2[fromPath][mod];
1068
1425
  }
1069
1426
  }
1070
1427
  return true;
1071
1428
  }
1072
- // src/parser/import-module.ts
1073
- function countTabsBeforeSubstring(str, sub, tabChar) {
1074
- const idx = str.indexOf(sub);
1075
- if (idx === -1)
1076
- return -1;
1077
- const lineStart = str.lastIndexOf(`
1078
- `, idx - 1) + 1;
1079
- const segment = str.slice(lineStart, idx);
1080
- let count = 0;
1081
- for (const ch of segment) {
1082
- if (ch === tabChar)
1083
- count++;
1084
- else
1085
- break;
1086
- }
1087
- return count;
1429
+ // src/parser/class-implement.ts
1430
+ function resolveOptionalParams(str) {
1431
+ const funcExprRegex = /(\w+)\s*=\s*function\s*\(([^)]*)\)/g;
1432
+ return str.replace(funcExprRegex, (match, name, params) => {
1433
+ const resolvedParams = params.replace(/\?/g, " = undefined");
1434
+ return `${name} = function(${resolvedParams})`;
1435
+ });
1088
1436
  }
1089
- function resolveImportPath(filePath, importPath, config) {
1090
- if (!config.path)
1091
- return resolvePath(`${filePath}/${importPath}`);
1092
- const useWildcard = Object.keys(config.path).filter((k) => k.endsWith("*")).find((k) => importPath.startsWith(k.slice(0, -1)));
1093
- if (useWildcard) {
1094
- const pathAlias = config.path[useWildcard]?.replace("*", "");
1095
- const dynPath = importPath.replace(useWildcard.slice(0, -2), "");
1096
- if (!pathAlias) {
1097
- log.error(`Path \x1B[33m${importPath}\x1B[0m not found in path aliases. Aborting...`);
1098
- return "";
1099
- }
1100
- if (pathAlias.startsWith("~")) {
1101
- return resolvePath(`${config.source}/${pathAlias.replace("~/", "").replace("~", "")}${dynPath}`);
1102
- }
1103
- return resolvePath(`${filePath}/${pathAlias}${dynPath}`);
1104
- } else if (config.path[importPath]) {
1105
- const pathAlias = config.path[importPath];
1106
- if (pathAlias.startsWith("~")) {
1107
- return resolvePath(`${config.source}/${pathAlias.replace("~/", "").replace("~", "")}`);
1108
- }
1109
- return resolvePath(`${filePath}/${pathAlias}`);
1110
- }
1111
- return resolvePath(`${filePath}/${importPath}`);
1437
+ function convertArrowFn(str) {
1438
+ arrowFnHeaderRegex.lastIndex = 0;
1439
+ return str.replace(arrowFnHeaderRegex, (match, _p1, _p2, _offset, _input, groups) => {
1440
+ if (!groups?.name)
1441
+ return match;
1442
+ const rawParams = groups.params?.trim() ?? "";
1443
+ const paramsSource = rawParams.startsWith("(") ? rawParams : `(${rawParams})`;
1444
+ const params = parseFnParams(paramsSource);
1445
+ return `${groups.name} = function(${params.combined.join(", ")})`;
1446
+ });
1112
1447
  }
1113
- async function getModuleUsage(module2, fileGroup, file, config) {
1114
- const matches = [...file.content.matchAll(modControlRegex)];
1115
- const limit = 10;
1448
+ function parseHeader(str, regex = implHeaderRegex) {
1116
1449
  const results = [];
1117
- let isInvalid = false;
1118
- for (let i = 0;i < matches.length; i += limit) {
1119
- const batch = matches.slice(i, i + limit);
1120
- const res = await Promise.all(batch.map(async (match) => {
1121
- const { cmd, mod, path: path3 } = match.groups;
1122
- const res2 = {
1123
- cmd: null,
1124
- files: null,
1125
- modList: null,
1126
- targetPath: null,
1127
- targetStr: ""
1128
- };
1129
- if (!(cmd && mod && path3)) {
1130
- log.error(`Invalid module control statement: \x1B[34m${cmd} ${mod} from ${path3}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
1131
- isInvalid = true;
1132
- return null;
1133
- }
1134
- const fromPath = normalizePath(resolveImportPath(file.path, path3.slice(1, -1), config));
1135
- const modList = {};
1136
- const alias = {};
1137
- if (!module2[fromPath]) {
1138
- if (cmd === "include" && (mod.startsWith("{") && (mod.includes('"') || mod.includes("'")))) {
1139
- const files = mod.slice(1, -1).split(",").map((m) => m.trim());
1140
- const filePaths = [];
1141
- for (const f of files) {
1142
- const filePath = normalizePath(resolvePath(`${file.path}/${f.slice(1, -1)}`));
1143
- const targetFile = fileGroup.normal.find((fl) => {
1144
- let targetFile2 = `${fl.path}/${fl.name}`;
1145
- let currFile = filePath;
1146
- if (!targetFile2.endsWith(".gml"))
1147
- targetFile2 += ".gml";
1148
- if (!currFile.endsWith(".gml"))
1149
- currFile += ".gml";
1150
- return targetFile2 === currFile;
1151
- });
1152
- if (targetFile)
1153
- filePaths.push(targetFile);
1154
- else if (await fileExists(filePath))
1155
- filePaths.push(filePath);
1156
- else {
1157
- if (config.onNotFound === "error") {
1158
- log.error(`File \x1B[33m${f.slice(1, -1)}\x1B[0m from \x1B[32m${file.path}\x1B[0m not found. Aborting...`);
1159
- isInvalid = true;
1160
- return null;
1161
- } else
1162
- log.warn(`File \x1B[33m${f.slice(1, -1)}\x1B[0m from \x1B[32m${file.path}\x1B[0m not found. Skipping this file...`);
1163
- }
1164
- }
1165
- return {
1166
- cmd,
1167
- files: filePaths,
1168
- modList,
1169
- targetPath: fromPath,
1170
- targetStr: match[0]
1171
- };
1172
- }
1173
- log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
1174
- isInvalid = true;
1175
- return null;
1176
- }
1177
- if (mod === "*" || mod.startsWith("{") && mod.endsWith("}")) {
1178
- const targetMods = mod === "*" ? null : mod.slice(1, -1).split(",").map((m) => {
1179
- const split = m.split(":");
1180
- const key = split[0].trim();
1181
- if (split.length === 1)
1182
- alias[key] = key;
1183
- else
1184
- alias[key] = split[1].trim();
1185
- if (!module2[fromPath]) {
1186
- if (config.onNotFound === "error") {
1187
- log.error(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Aborting...`);
1188
- isInvalid = true;
1189
- } else
1190
- log.warn(`Path \x1B[33m${fromPath}\x1B[0m doesn't have any exported modules. Skipping this module...`);
1191
- return null;
1192
- }
1193
- if (!module2[fromPath][key]) {
1194
- if (config.onNotFound === "error") {
1195
- log.error(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
1196
- isInvalid = true;
1197
- } else
1198
- log.warn(`Module \x1B[33m${key}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
1199
- return null;
1200
- }
1201
- return key;
1202
- }).filter(Boolean);
1203
- Object.entries(module2[fromPath]).forEach(([key, value]) => {
1204
- if (mod === "*")
1205
- modList[key] = { name: key, as: key, value, usingAlias: false };
1206
- else if (targetMods.includes(key)) {
1207
- const usingAlias = key in alias && alias[key] !== key;
1208
- modList[alias[key] ?? key] = { name: key, as: alias[key] ?? key, value, usingAlias };
1209
- if (usingAlias) {
1210
- if (!module2[fromPath])
1211
- module2[fromPath] = {};
1212
- module2[fromPath][`@${alias[key]}`] = value;
1213
- }
1214
- }
1215
- });
1450
+ let match;
1451
+ while ((match = regex.exec(str)) !== null) {
1452
+ const name = match.groups.name;
1453
+ const start = regex.lastIndex;
1454
+ let braceCount = 1;
1455
+ let inString = false;
1456
+ let stringChar = "";
1457
+ let i = start;
1458
+ for (;i < str.length; i++) {
1459
+ const char = str[i];
1460
+ if (!inString && char === "/" && str[i + 1] === "/") {
1461
+ while (i < str.length && str[i] !== `
1462
+ `)
1463
+ i++;
1464
+ continue;
1465
+ }
1466
+ if (inString) {
1467
+ if (char === stringChar && (i === 0 || str[i - 1] !== "\\")) {
1468
+ inString = false;
1469
+ }
1216
1470
  } else {
1217
- if (!module2[fromPath][mod]) {
1218
- if (config.onNotFound === "error") {
1219
- log.error(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Aborting...`);
1220
- isInvalid = true;
1221
- } else
1222
- log.warn(`Module \x1B[33m${mod}\x1B[0m from \x1B[32m${fromPath}\x1B[0m not found. Skipping this module...`);
1223
- return res2;
1471
+ if (char === '"' || char === "'") {
1472
+ inString = true;
1473
+ stringChar = char;
1474
+ } else if (char === "{") {
1475
+ braceCount++;
1476
+ } else if (char === "}") {
1477
+ braceCount--;
1478
+ if (braceCount === 0)
1479
+ break;
1224
1480
  }
1225
- modList[mod] = { name: mod, as: mod, value: module2[fromPath][mod], usingAlias: false };
1226
1481
  }
1227
- return {
1228
- cmd,
1229
- files: null,
1230
- modList,
1231
- targetPath: fromPath,
1232
- targetStr: match[0]
1233
- };
1234
- }));
1235
- if (isInvalid)
1236
- break;
1237
- results.push(...res);
1482
+ }
1483
+ const body = str.slice(start, i);
1484
+ results.push({ name, body });
1485
+ regex.lastIndex = i + 1;
1238
1486
  }
1239
- return !isInvalid && results.every((r) => r) ? results : null;
1487
+ return results;
1240
1488
  }
1241
- async function implementModules(module2, fileGroup, file, config, mods) {
1242
- if (!mods)
1243
- mods = await getModuleUsage(module2, fileGroup, file, config);
1244
- if (!mods)
1245
- return null;
1246
- const toRemove = [...file.content.matchAll(modControlRegex)];
1247
- for (const rm of toRemove) {
1248
- if (rm.groups?.cmd !== "include")
1249
- file.content = file.content.replace(rm[0], "");
1489
+ function implementClass(module2, fileGroup, config) {
1490
+ if (fileGroup.generate.length == 0 && fileGroup.scaff.length == 0) {
1491
+ log.warn("No files to implement classes from.");
1492
+ return false;
1250
1493
  }
1251
- for (const mod of mods) {
1252
- if (!mod)
1253
- return null;
1254
- if (!mod.cmd)
1255
- return null;
1256
- switch (mod.cmd) {
1257
- case "export":
1258
- const thisPath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1259
- if (!module2[thisPath])
1260
- module2[thisPath] = {};
1261
- Object.entries(mod.modList).forEach(([_, value]) => {
1262
- module2[thisPath][value.as] = value.value;
1263
- });
1264
- break;
1265
- case "include":
1266
- let toReplace = "";
1267
- if (!mod.files) {
1268
- if (!mod.modList)
1269
- return null;
1270
- const modEntries = Object.entries(mod.modList);
1271
- const modLen = modEntries.length;
1272
- const modIterator = modEntries.entries();
1273
- for (const [idx, [_, include]] of modIterator) {
1274
- if (idx > 0)
1275
- switch (include.value.type) {
1276
- case "object":
1277
- case "method":
1278
- case "array":
1279
- case "enum":
1280
- toReplace += `
1281
- `;
1282
- break;
1283
- }
1284
- toReplace += include.value.parsedStr + `
1285
- `;
1286
- if (idx === modLen - 1)
1287
- toReplace += `
1288
- `;
1289
- }
1290
- file.content = file.content.replace(mod.targetStr, toReplace);
1291
- } else {
1292
- for (const fileOrPath of mod.files) {
1293
- if (typeof fileOrPath === "string") {
1294
- const content = await fsRuntime.readText(fileOrPath);
1295
- toReplace += content + `
1296
-
1297
- `;
1298
- } else
1299
- toReplace += fileOrPath.content + `
1300
-
1301
- `;
1494
+ const toImpl = [];
1495
+ for (const file of fileGroup.scaff) {
1496
+ if (file.childs.length > 0)
1497
+ file.childs.forEach((child) => toImpl.push({ parent: file, file: child }));
1498
+ }
1499
+ for (const file of fileGroup.generate) {
1500
+ if (file.childs.length > 0)
1501
+ file.childs.forEach((child) => toImpl.push({ parent: file, file: child }));
1502
+ }
1503
+ for (const fileImpl of toImpl) {
1504
+ const filePath = fileImpl.parent.isIndex ? fileImpl.parent.path : `${fileImpl.parent.path}/${fileImpl.parent.name}`;
1505
+ const match = parseHeader(fileImpl.file.content);
1506
+ const classNames = [];
1507
+ for (const [_, m] of match.entries()) {
1508
+ const { name: className } = m;
1509
+ let { body } = m;
1510
+ body = resolveOptionalParams(body);
1511
+ body = convertClassMethods(body);
1512
+ body = convertArrowFn(body);
1513
+ if (!className || !body)
1514
+ continue;
1515
+ classNames.push(className);
1516
+ if (!module2[filePath] || !module2[filePath][className]) {
1517
+ let newFilePath = null;
1518
+ for (const file of fileGroup.scaff) {
1519
+ if (file.content.includes(`class ${className} {`)) {
1520
+ newFilePath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1521
+ break;
1302
1522
  }
1303
- file.content = file.content.replace(mod.targetStr, toReplace);
1304
1523
  }
1305
- break;
1306
- case "import":
1307
- const cmdMatches = [...file.content.matchAll(contentModRegex)];
1308
- const shortCmdMatches = [...file.content.matchAll(contentModShortRegex)];
1309
- const useMatches = [...file.content.matchAll(useModRegex)];
1310
- for (const match of cmdMatches) {
1311
- const { cmd: contentCmd, mod: contentMod } = match.groups;
1312
- if (!contentCmd || !contentMod) {
1313
- log.error(`Invalid content module statement: \x1B[34m${contentCmd} ${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
1314
- return null;
1315
- }
1316
- if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
1317
- continue;
1318
- if (config.debugLevel >= 1)
1319
- log.debug(`Content module statement found: \x1B[34m${contentCmd} ${contentMod}\x1B[0m in \x1B[33m${file.name === "" ? "index" : file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
1320
- let parsedStr = "";
1321
- switch (contentCmd) {
1322
- case "content":
1323
- const tabChar = config.tabType === "1t" ? "\t" : config.tabType === "2s" ? " " : " ";
1324
- const tabCnt = countTabsBeforeSubstring(file.content, match[0], tabChar);
1325
- parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].parsedStr;
1326
- if (tabCnt > 0) {
1327
- const tabLevels = getTabLevels(parsedStr, config.tabType).map((l) => l + tabCnt);
1328
- file.content = file.content.replace(match[0], parsedStr.split(`
1329
- `).map((l, i) => tabLevels[i] ? insertTabs(tabLevels[i], config.tabType) + l : l).join(`
1330
- `));
1331
- } else
1332
- file.content = file.content.replace(match[0], parsedStr);
1333
- break;
1334
- case "nameof":
1335
- parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].name;
1336
- file.content = file.content.replace(`@nameof ${contentMod}`, parsedStr);
1337
- break;
1338
- case "typeof":
1339
- parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].type;
1340
- file.content = file.content.replace(`@typeof ${contentMod}`, `"${parsedStr}"`);
1341
- break;
1342
- case "valueof":
1343
- parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].value;
1344
- file.content = file.content.replace(`@valueof ${contentMod}`, parsedStr);
1524
+ if (!newFilePath) {
1525
+ for (const file of fileGroup.generate) {
1526
+ if (file.content.includes(`class ${className} {`)) {
1527
+ newFilePath = file.isIndex ? file.path : `${file.path}/${file.name}`;
1345
1528
  break;
1529
+ }
1346
1530
  }
1347
1531
  }
1348
- for (const match of shortCmdMatches) {
1349
- const { mod: contentMod } = match.groups;
1350
- if (!contentMod) {
1351
- log.error(`Invalid content module statement: \x1B[34m@:${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
1352
- return null;
1353
- }
1354
- if (config.debugLevel >= 1)
1355
- log.debug(`Valueof module statement found: \x1B[34m${contentMod}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
1356
- if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
1532
+ if (!newFilePath) {
1533
+ if (config.onNotFound === "error") {
1534
+ log.error(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Aborting...`);
1535
+ return false;
1536
+ } else {
1537
+ log.warn(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Skipping this class...`);
1357
1538
  continue;
1358
- const parsedStr = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod].value;
1359
- file.content = file.content.replace(`@:${contentMod}`, parsedStr);
1360
- }
1361
- for (const match of useMatches) {
1362
- const { mod: contentMod, body } = match.groups;
1363
- if (!contentMod || !body) {
1364
- log.error(`Invalid content module statement: \x1B[34m@use ${contentMod} ${body}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m. Aborting...`);
1365
- return null;
1366
1539
  }
1367
- if (!(`@${contentMod}` in mod.modList) && !(contentMod in mod.modList))
1540
+ }
1541
+ if (module2[newFilePath][className].type === "class") {
1542
+ const newMod = module2[newFilePath][className];
1543
+ newMod.body += `
1544
+ ${body}`;
1545
+ } else {
1546
+ if (config.onNotFound === "error") {
1547
+ log.error(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Aborting...`);
1548
+ return false;
1549
+ } else {
1550
+ log.warn(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Skipping this class...`);
1368
1551
  continue;
1369
- if (config.debugLevel >= 1)
1370
- log.debug(`Use module statement found: \x1B[34m@use ${contentMod} ${body}\x1B[0m in \x1B[33m${file.name}\x1B[0m from \x1B[32m${file.path}\x1B[0m.`);
1371
- const lines = body.slice(1, -1).split(`
1372
- `).filter(Boolean);
1373
- const pairs = lines.map((l) => l.split(":").map((p) => {
1374
- const delimIdx = p.lastIndexOf(",");
1375
- return p.slice(0, delimIdx === -1 ? undefined : delimIdx).trim();
1376
- }));
1377
- const tabChar = config.tabType === "1t" ? "\t" : config.tabType === "2s" ? " " : " ";
1378
- const tabCnt = countTabsBeforeSubstring(file.content.slice(file.content.lastIndexOf(`
1379
- `, match.index), match.index + match[0].length), "@", tabChar);
1380
- const tabLevels = getTabLevels(body, config.tabType).map((l) => l + tabCnt);
1381
- const currMod = module2[mod.targetPath][`@${contentMod}` in module2[mod.targetPath] ? `@${contentMod}` : contentMod];
1382
- const modMember = Object.entries(currMod.member);
1383
- const tabLevel = tabCnt + tabLevels[Math.min(1, tabLevels.length - 1)];
1384
- let res = `{
1385
- `;
1386
- for (const [idx, [mName, mVal]] of modMember.entries()) {
1387
- const pairIdx = pairs.findIndex((p) => p[0] === mName);
1388
- const pair = pairIdx > -1 ? pairs[pairIdx] : null;
1389
- const value = pair ? pair.length === 2 ? pair[1] : mVal.value : mVal.value;
1390
- if (value) {
1391
- res += `${insertTabs(tabLevel, config.tabType)}${mName}: ${value}${idx < modMember.length - 2 ? "," : ""}
1392
- `;
1393
- }
1394
- if (pairIdx > -1)
1395
- swapAndPop(pairs, pairIdx);
1396
- }
1397
- for (const [idx, pair] of pairs.entries()) {
1398
- if (idx === 0)
1399
- res = res.slice(0, -1) + `,
1400
- `;
1401
- res += `${insertTabs(tabLevel, config.tabType)}${pair[0]}: ${pair[1]}${idx < pairs.length - 1 ? "," : ""}
1402
- `;
1403
1552
  }
1404
- res += insertTabs(tabCnt, config.tabType) + "}";
1405
- file.content = file.content.replace(match[0], res);
1406
1553
  }
1407
- break;
1554
+ continue;
1555
+ } else if (module2[filePath][className].type === "class") {
1556
+ module2[filePath][className].body += `
1557
+ ${body}`;
1558
+ } else {
1559
+ if (config.onNotFound === "error") {
1560
+ log.error(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Aborting...`);
1561
+ return false;
1562
+ } else {
1563
+ log.warn(`Class \x1B[33m${className}\x1B[0m not found for file \x1B[34m${fileImpl.file.name}\x1B[0m. Skipping this class...`);
1564
+ continue;
1565
+ }
1566
+ }
1408
1567
  }
1409
- file.content = file.content.trim() + `
1410
- `;
1411
1568
  }
1412
- return mods;
1569
+ return true;
1413
1570
  }
1414
1571
  // types/gm-event.ts
1415
1572
  var EVENT = {
@@ -2370,6 +2527,9 @@ async function main() {
2370
2527
  return;
2371
2528
  log.debug("Classes implemented successfully.");
2372
2529
  log.debug("Implementing modules...");
2530
+ for (const file of files) {
2531
+ reexportModule(module2, file, config);
2532
+ }
2373
2533
  const implMods = [];
2374
2534
  for (const file of files) {
2375
2535
  const mod = await implementModules(module2, fileGroup, file, config);
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
- {
2
- "name": "@scaffscript/core",
3
- "version": "0.2.3",
4
- "repository": {
5
- "type": "git",
6
- "url": "https://github.com/undervolta/scaffscript"
7
- },
8
- "main": "dist/index.cjs",
9
- "devDependencies": {
10
- "@types/bun": "latest"
11
- },
12
- "peerDependencies": {
13
- "typescript": "^5"
14
- },
15
- "bin": {
16
- "scaff": "./dist/index.cjs"
17
- },
18
- "description": "A minimal superset language of GML with TypeScript-like module system",
19
- "files": [
20
- "dist"
21
- ],
22
- "keywords": [
23
- "gamemaker",
24
- "gml",
25
- "scaff",
26
- "script",
27
- "superset",
28
- "module",
29
- "cli"
30
- ],
31
- "license": "MIT",
32
- "scripts": {
33
- "build": "bun run build:all",
34
- "build:node": "bun build src/index-node.ts --outfile dist/index.cjs --target node --format cjs",
35
- "build:bun": "bun build src/index-bun.ts --outfile build/index.mjs --target bun --format esm",
36
- "build:all": "bun run build:node && bun run build:bun",
37
- "dev": "bun run src/index-node.ts",
38
- "prelink": "bun run build"
39
- },
40
- "type": "module"
41
- }
1
+ {
2
+ "name": "@scaffscript/core",
3
+ "version": "0.2.6",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/undervolta/scaffscript"
7
+ },
8
+ "main": "dist/index.cjs",
9
+ "devDependencies": {
10
+ "@types/bun": "^1.3.11"
11
+ },
12
+ "peerDependencies": {
13
+ "typescript": "^5"
14
+ },
15
+ "bin": {
16
+ "scaff": "./dist/index.cjs"
17
+ },
18
+ "description": "A minimal superset language of GML with TypeScript-like module system",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "keywords": [
23
+ "gamemaker",
24
+ "gml",
25
+ "scaff",
26
+ "script",
27
+ "superset",
28
+ "module",
29
+ "cli"
30
+ ],
31
+ "license": "MIT",
32
+ "scripts": {
33
+ "build": "bun run build:all",
34
+ "build:node": "bun build src/index-node.ts --outfile dist/index.cjs --target node --format cjs",
35
+ "build:bun": "bun build src/index-bun.ts --outfile build/index.mjs --target bun --format esm",
36
+ "build:all": "bun run build:node && bun run build:bun",
37
+ "dev": "bun run src/index-node.ts",
38
+ "prelink": "bun run build"
39
+ },
40
+ "type": "module"
41
+ }