redscript-mc 1.2.17 → 1.2.18

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.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Compile-all smoke test
3
+ *
4
+ * Finds every .mcrs file in the repo (excluding declaration files and node_modules)
5
+ * and verifies that each one compiles without throwing an error.
6
+ *
7
+ * This catches regressions where a language change breaks existing source files.
8
+ */
9
+ export {};
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * Compile-all smoke test
4
+ *
5
+ * Finds every .mcrs file in the repo (excluding declaration files and node_modules)
6
+ * and verifies that each one compiles without throwing an error.
7
+ *
8
+ * This catches regressions where a language change breaks existing source files.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const compile_1 = require("../compile");
47
+ const REPO_ROOT = path.resolve(__dirname, '../../');
48
+ /** Patterns to skip */
49
+ const SKIP_GLOBS = [
50
+ 'node_modules',
51
+ '.git',
52
+ 'builtins.d.mcrs', // declaration-only file, not valid source
53
+ 'editors/', // copy of builtins.d.mcrs
54
+ ];
55
+ function shouldSkip(filePath) {
56
+ const rel = path.relative(REPO_ROOT, filePath);
57
+ return SKIP_GLOBS.some(pat => rel.includes(pat));
58
+ }
59
+ function findMcrsFiles(dir) {
60
+ const results = [];
61
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
62
+ const fullPath = path.join(dir, entry.name);
63
+ if (shouldSkip(fullPath))
64
+ continue;
65
+ if (entry.isDirectory()) {
66
+ results.push(...findMcrsFiles(fullPath));
67
+ }
68
+ else if (entry.isFile() && entry.name.endsWith('.mcrs')) {
69
+ results.push(fullPath);
70
+ }
71
+ }
72
+ return results;
73
+ }
74
+ const mcrsFiles = findMcrsFiles(REPO_ROOT);
75
+ describe('compile-all: every .mcrs file should compile without errors', () => {
76
+ test('found at least one .mcrs file', () => {
77
+ expect(mcrsFiles.length).toBeGreaterThan(0);
78
+ });
79
+ for (const filePath of mcrsFiles) {
80
+ const label = path.relative(REPO_ROOT, filePath);
81
+ test(label, () => {
82
+ const source = fs.readFileSync(filePath, 'utf8');
83
+ // Should not throw
84
+ expect(() => {
85
+ (0, compile_1.compile)(source, { namespace: 'smoke_test', optimize: false });
86
+ }).not.toThrow();
87
+ });
88
+ }
89
+ });
90
+ //# sourceMappingURL=compile-all.test.js.map
@@ -5,7 +5,7 @@
5
5
  * Handles special cases like entity selectors vs decorators,
6
6
  * range literals, and raw commands.
7
7
  */
8
- export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
8
+ export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'declare' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
9
9
  export interface Token {
10
10
  kind: TokenKind;
11
11
  value: string;
@@ -37,6 +37,7 @@ const KEYWORDS = {
37
37
  execute: 'execute',
38
38
  run: 'run',
39
39
  unless: 'unless',
40
+ declare: 'declare',
40
41
  int: 'int',
41
42
  bool: 'bool',
42
43
  float: 'float',
@@ -372,7 +372,26 @@ class Lowering {
372
372
  if (macroParam) {
373
373
  return { str: `$(${macroParam})`, macroParam };
374
374
  }
375
- // Handle ~ident (e.g. ~height) - relative coord with variable offset
375
+ // Handle ~ident / ^ident syntax relative/local coord with a VARIABLE offset.
376
+ //
377
+ // WHY macros are required here:
378
+ // Minecraft's ~N and ^N coordinate syntax requires N to be a compile-time
379
+ // literal number. There is no command that accepts a scoreboard value as a
380
+ // relative offset. Therefore `~height` (where height is a runtime int) can
381
+ // only be expressed at the MC level via the 1.20.2+ function macro system,
382
+ // which substitutes $(height) into the command text at call time.
383
+ //
384
+ // Contrast with absolute coords: `tp(target, x, y, z)` where x/y/z are
385
+ // plain ints — those become $(x) etc. as literal replacements, same mechanism,
386
+ // but the distinction matters to callers: ~$(height) means "relative by height
387
+ // blocks from current pos", not "teleport to absolute scoreboard value".
388
+ //
389
+ // Example:
390
+ // fn launch_up(target: selector, height: int) {
391
+ // tp(target, ~0, ~height, ~0); // "~height" parsed as rel_coord
392
+ // }
393
+ // Emits: $tp $(target) ~0 ~$(height) ~0
394
+ // Called: function ns:launch_up with storage rs:macro_args
376
395
  if (expr.kind === 'rel_coord' || expr.kind === 'local_coord') {
377
396
  const val = expr.value; // e.g. "~height" or "^depth"
378
397
  const prefix = val[0]; // ~ or ^
@@ -27,6 +27,8 @@ export declare class Parser {
27
27
  private parseConstDecl;
28
28
  private parseGlobalDecl;
29
29
  private parseFnDecl;
30
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
31
+ private parseDeclareStub;
30
32
  private parseDecorators;
31
33
  private parseDecoratorValue;
32
34
  private parseParams;
@@ -149,6 +149,11 @@ class Parser {
149
149
  else if (this.check('const')) {
150
150
  consts.push(this.parseConstDecl());
151
151
  }
152
+ else if (this.check('declare')) {
153
+ // Declaration-only stub (e.g. from builtins.d.mcrs) — parse and discard
154
+ this.advance(); // consume 'declare'
155
+ this.parseDeclareStub();
156
+ }
152
157
  else {
153
158
  declarations.push(this.parseFnDecl());
154
159
  }
@@ -254,6 +259,26 @@ class Parser {
254
259
  const body = this.parseBlock();
255
260
  return this.withLoc({ name, params, returnType, decorators, body }, fnToken);
256
261
  }
262
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
263
+ parseDeclareStub() {
264
+ this.expect('fn');
265
+ this.expect('ident'); // name
266
+ this.expect('(');
267
+ // consume params until ')'
268
+ let depth = 1;
269
+ while (!this.check('eof') && depth > 0) {
270
+ const t = this.advance();
271
+ if (t.kind === '(')
272
+ depth++;
273
+ else if (t.kind === ')')
274
+ depth--;
275
+ }
276
+ // optional return type annotation `: type` or `-> type`
277
+ if (this.match(':') || this.match('->')) {
278
+ this.parseType();
279
+ }
280
+ this.match(';'); // consume trailing semicolon
281
+ }
257
282
  parseDecorators() {
258
283
  const decorators = [];
259
284
  while (this.check('decorator')) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "redscript-vscode",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "redscript-vscode",
9
- "version": "1.0.7",
9
+ "version": "1.0.9",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "redscript": "file:../../"
@@ -2,7 +2,7 @@
2
2
  "name": "redscript-vscode",
3
3
  "displayName": "RedScript for Minecraft",
4
4
  "description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
5
- "version": "1.0.7",
5
+ "version": "1.0.9",
6
6
  "publisher": "bkmashiro",
7
7
  "icon": "icon.png",
8
8
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.17",
3
+ "version": "1.2.18",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Compile-all smoke test
3
+ *
4
+ * Finds every .mcrs file in the repo (excluding declaration files and node_modules)
5
+ * and verifies that each one compiles without throwing an error.
6
+ *
7
+ * This catches regressions where a language change breaks existing source files.
8
+ */
9
+
10
+ import * as fs from 'fs'
11
+ import * as path from 'path'
12
+ import { compile } from '../compile'
13
+
14
+ const REPO_ROOT = path.resolve(__dirname, '../../')
15
+
16
+ /** Patterns to skip */
17
+ const SKIP_GLOBS = [
18
+ 'node_modules',
19
+ '.git',
20
+ 'builtins.d.mcrs', // declaration-only file, not valid source
21
+ 'editors/', // copy of builtins.d.mcrs
22
+ ]
23
+
24
+ function shouldSkip(filePath: string): boolean {
25
+ const rel = path.relative(REPO_ROOT, filePath)
26
+ return SKIP_GLOBS.some(pat => rel.includes(pat))
27
+ }
28
+
29
+ function findMcrsFiles(dir: string): string[] {
30
+ const results: string[] = []
31
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
32
+ const fullPath = path.join(dir, entry.name)
33
+ if (shouldSkip(fullPath)) continue
34
+ if (entry.isDirectory()) {
35
+ results.push(...findMcrsFiles(fullPath))
36
+ } else if (entry.isFile() && entry.name.endsWith('.mcrs')) {
37
+ results.push(fullPath)
38
+ }
39
+ }
40
+ return results
41
+ }
42
+
43
+ const mcrsFiles = findMcrsFiles(REPO_ROOT)
44
+
45
+ describe('compile-all: every .mcrs file should compile without errors', () => {
46
+ test('found at least one .mcrs file', () => {
47
+ expect(mcrsFiles.length).toBeGreaterThan(0)
48
+ })
49
+
50
+ for (const filePath of mcrsFiles) {
51
+ const label = path.relative(REPO_ROOT, filePath)
52
+ test(label, () => {
53
+ const source = fs.readFileSync(filePath, 'utf8')
54
+ // Should not throw
55
+ expect(() => {
56
+ compile(source, { namespace: 'smoke_test', optimize: false })
57
+ }).not.toThrow()
58
+ })
59
+ }
60
+ })
@@ -16,7 +16,7 @@ export type TokenKind =
16
16
  // Keywords
17
17
  | 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match'
18
18
  | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace'
19
- | 'execute' | 'run' | 'unless'
19
+ | 'execute' | 'run' | 'unless' | 'declare'
20
20
  // Types
21
21
  | 'int' | 'bool' | 'float' | 'string' | 'void'
22
22
  | 'BlockPos'
@@ -89,6 +89,7 @@ const KEYWORDS: Record<string, TokenKind> = {
89
89
  execute: 'execute',
90
90
  run: 'run',
91
91
  unless: 'unless',
92
+ declare: 'declare',
92
93
  int: 'int',
93
94
  bool: 'bool',
94
95
  float: 'float',
@@ -387,7 +387,26 @@ export class Lowering {
387
387
  if (macroParam) {
388
388
  return { str: `$(${macroParam})`, macroParam }
389
389
  }
390
- // Handle ~ident (e.g. ~height) - relative coord with variable offset
390
+ // Handle ~ident / ^ident syntax relative/local coord with a VARIABLE offset.
391
+ //
392
+ // WHY macros are required here:
393
+ // Minecraft's ~N and ^N coordinate syntax requires N to be a compile-time
394
+ // literal number. There is no command that accepts a scoreboard value as a
395
+ // relative offset. Therefore `~height` (where height is a runtime int) can
396
+ // only be expressed at the MC level via the 1.20.2+ function macro system,
397
+ // which substitutes $(height) into the command text at call time.
398
+ //
399
+ // Contrast with absolute coords: `tp(target, x, y, z)` where x/y/z are
400
+ // plain ints — those become $(x) etc. as literal replacements, same mechanism,
401
+ // but the distinction matters to callers: ~$(height) means "relative by height
402
+ // blocks from current pos", not "teleport to absolute scoreboard value".
403
+ //
404
+ // Example:
405
+ // fn launch_up(target: selector, height: int) {
406
+ // tp(target, ~0, ~height, ~0); // "~height" parsed as rel_coord
407
+ // }
408
+ // Emits: $tp $(target) ~0 ~$(height) ~0
409
+ // Called: function ns:launch_up with storage rs:macro_args
391
410
  if (expr.kind === 'rel_coord' || expr.kind === 'local_coord') {
392
411
  const val = expr.value // e.g. "~height" or "^depth"
393
412
  const prefix = val[0] // ~ or ^
@@ -180,6 +180,10 @@ export class Parser {
180
180
  enums.push(this.parseEnumDecl())
181
181
  } else if (this.check('const')) {
182
182
  consts.push(this.parseConstDecl())
183
+ } else if (this.check('declare')) {
184
+ // Declaration-only stub (e.g. from builtins.d.mcrs) — parse and discard
185
+ this.advance() // consume 'declare'
186
+ this.parseDeclareStub()
183
187
  } else {
184
188
  declarations.push(this.parseFnDecl())
185
189
  }
@@ -311,6 +315,25 @@ export class Parser {
311
315
  return this.withLoc({ name, params, returnType, decorators, body }, fnToken)
312
316
  }
313
317
 
318
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
319
+ private parseDeclareStub(): void {
320
+ this.expect('fn')
321
+ this.expect('ident') // name
322
+ this.expect('(')
323
+ // consume params until ')'
324
+ let depth = 1
325
+ while (!this.check('eof') && depth > 0) {
326
+ const t = this.advance()
327
+ if (t.kind === '(') depth++
328
+ else if (t.kind === ')') depth--
329
+ }
330
+ // optional return type annotation `: type` or `-> type`
331
+ if (this.match(':') || this.match('->')) {
332
+ this.parseType()
333
+ }
334
+ this.match(';') // consume trailing semicolon
335
+ }
336
+
314
337
  private parseDecorators(): Decorator[] {
315
338
  const decorators: Decorator[] = []
316
339