@sayue_ltr/fleq 1.50.1 → 2.0.0

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 (99) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +42 -6
  3. package/dist/config.js +37 -4
  4. package/dist/dmdata/rest-client.js +58 -3
  5. package/dist/dmdata/telegram-parser.js +115 -64
  6. package/dist/dmdata/ws-client.js +49 -18
  7. package/dist/engine/cli/cli-run.js +88 -3
  8. package/dist/engine/cli/cli.js +12 -0
  9. package/dist/engine/eew/eew-tracker.js +41 -15
  10. package/dist/engine/filter/compile-filter.js +21 -0
  11. package/dist/engine/filter/compiler.js +188 -0
  12. package/dist/engine/filter/errors.js +41 -0
  13. package/dist/engine/filter/field-registry.js +78 -0
  14. package/dist/engine/filter/index.js +15 -0
  15. package/dist/engine/filter/parser.js +137 -0
  16. package/dist/engine/filter/rank-maps.js +34 -0
  17. package/dist/engine/filter/tokenizer.js +121 -0
  18. package/dist/engine/filter/type-checker.js +104 -0
  19. package/dist/engine/filter/types.js +2 -0
  20. package/dist/engine/filter-template/pipeline-controller.js +73 -0
  21. package/dist/engine/filter-template/pipeline.js +16 -0
  22. package/dist/engine/messages/display-callbacks.js +7 -0
  23. package/dist/engine/messages/message-router.js +114 -182
  24. package/dist/engine/messages/summary-tracker.js +106 -0
  25. package/dist/engine/messages/telegram-stats.js +103 -0
  26. package/dist/engine/messages/volcano-route-handler.js +122 -0
  27. package/dist/engine/monitor/monitor.js +52 -4
  28. package/dist/engine/monitor/shutdown.js +1 -0
  29. package/dist/engine/notification/notifier.js +21 -4
  30. package/dist/engine/notification/sound-player.js +398 -36
  31. package/dist/engine/presentation/diff-store.js +158 -0
  32. package/dist/engine/presentation/diff-types.js +2 -0
  33. package/dist/engine/presentation/events/from-earthquake.js +53 -0
  34. package/dist/engine/presentation/events/from-eew.js +72 -0
  35. package/dist/engine/presentation/events/from-lg-observation.js +58 -0
  36. package/dist/engine/presentation/events/from-nankai-trough.js +39 -0
  37. package/dist/engine/presentation/events/from-raw.js +35 -0
  38. package/dist/engine/presentation/events/from-seismic-text.js +37 -0
  39. package/dist/engine/presentation/events/from-tsunami.js +51 -0
  40. package/dist/engine/presentation/events/from-volcano.js +88 -0
  41. package/dist/engine/presentation/events/to-presentation-event.js +32 -0
  42. package/dist/engine/presentation/level-helpers.js +118 -0
  43. package/dist/engine/presentation/processors/process-earthquake.js +36 -0
  44. package/dist/engine/presentation/processors/process-eew.js +105 -0
  45. package/dist/engine/presentation/processors/process-lg-observation.js +30 -0
  46. package/dist/engine/presentation/processors/process-message.js +53 -0
  47. package/dist/engine/presentation/processors/process-nankai-trough.js +30 -0
  48. package/dist/engine/presentation/processors/process-raw.js +22 -0
  49. package/dist/engine/presentation/processors/process-seismic-text.js +30 -0
  50. package/dist/engine/presentation/processors/process-tsunami.js +42 -0
  51. package/dist/engine/presentation/processors/process-volcano.js +41 -0
  52. package/dist/engine/presentation/types.js +2 -0
  53. package/dist/engine/startup/config-resolver.js +2 -0
  54. package/dist/engine/template/compile-template.js +18 -0
  55. package/dist/engine/template/compiler.js +105 -0
  56. package/dist/engine/template/field-accessor.js +31 -0
  57. package/dist/engine/template/filters.js +100 -0
  58. package/dist/engine/template/index.js +5 -0
  59. package/dist/engine/template/parser.js +185 -0
  60. package/dist/engine/template/tokenizer.js +96 -0
  61. package/dist/engine/template/types.js +2 -0
  62. package/dist/types.js +3 -2
  63. package/dist/ui/display-adapter.js +60 -0
  64. package/dist/ui/earthquake-formatter.js +22 -5
  65. package/dist/ui/eew-formatter.js +25 -10
  66. package/dist/ui/formatter.js +116 -32
  67. package/dist/ui/minimap/grid-layout.js +91 -0
  68. package/dist/ui/minimap/index.js +16 -0
  69. package/dist/ui/minimap/minimap-renderer.js +277 -0
  70. package/dist/ui/minimap/pref-mapping.js +82 -0
  71. package/dist/ui/minimap/types.js +2 -0
  72. package/dist/ui/night-overlay.js +56 -0
  73. package/dist/ui/repl-handlers/command-definitions.js +327 -0
  74. package/dist/ui/repl-handlers/index.js +11 -0
  75. package/dist/ui/repl-handlers/info-handlers.js +633 -0
  76. package/dist/ui/repl-handlers/operation-handlers.js +233 -0
  77. package/dist/ui/repl-handlers/settings-handlers.js +927 -0
  78. package/dist/ui/repl-handlers/types.js +10 -0
  79. package/dist/ui/repl.js +81 -1752
  80. package/dist/ui/statistics-formatter.js +258 -0
  81. package/dist/ui/status-line.js +80 -0
  82. package/dist/ui/summary/index.js +5 -0
  83. package/dist/ui/summary/summary-line.js +18 -0
  84. package/dist/ui/summary/summary-model.js +31 -0
  85. package/dist/ui/summary/token-builders.js +317 -0
  86. package/dist/ui/summary/types.js +2 -0
  87. package/dist/ui/summary/width-fit.js +41 -0
  88. package/dist/ui/summary-interval-formatter.js +72 -0
  89. package/dist/ui/test-samples.js +6 -0
  90. package/dist/ui/theme.js +43 -5
  91. package/dist/ui/tip-shuffler.js +81 -0
  92. package/dist/ui/volcano-formatter.js +15 -13
  93. package/dist/ui/waiting-tips-eew.js +63 -0
  94. package/dist/ui/waiting-tips-info-systems.js +81 -0
  95. package/dist/ui/waiting-tips-seismology.js +97 -0
  96. package/dist/ui/waiting-tips-tsunami.js +72 -0
  97. package/dist/ui/waiting-tips-weather.js +189 -0
  98. package/dist/ui/waiting-tips.js +420 -249
  99. package/package.json +1 -1
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compile = compile;
4
+ const field_registry_1 = require("./field-registry");
5
+ const rank_maps_1 = require("./rank-maps");
6
+ function compile(ast) {
7
+ switch (ast.kind) {
8
+ case "or":
9
+ return compileOr(ast.children.map(compile));
10
+ case "and":
11
+ return compileAnd(ast.children.map(compile));
12
+ case "not":
13
+ return compileNot(compile(ast.operand));
14
+ case "truthy":
15
+ return compileTruthy(ast.value);
16
+ case "comparison":
17
+ return compileComparison(ast.left, ast.op, ast.right);
18
+ }
19
+ }
20
+ function compileOr(predicates) {
21
+ return (event) => predicates.some((p) => p(event));
22
+ }
23
+ function compileAnd(predicates) {
24
+ return (event) => predicates.every((p) => p(event));
25
+ }
26
+ function compileNot(predicate) {
27
+ return (event) => !predicate(event);
28
+ }
29
+ function compileTruthy(node) {
30
+ const getter = makeGetter(node);
31
+ return (event) => {
32
+ const val = getter(event);
33
+ return val != null && val !== false && val !== "" && val !== 0;
34
+ };
35
+ }
36
+ function compileComparison(left, op, right) {
37
+ const getLeft = makeGetter(left);
38
+ const getRight = makeGetter(right);
39
+ // フィールドの型情報を取得 (enum ランク変換用)
40
+ const leftField = left.kind === "path" ? (0, field_registry_1.resolveField)(left.segments.join(".")) : null;
41
+ const rankFn = leftField ? getRankFn(leftField.kind) : null;
42
+ switch (op) {
43
+ case "=":
44
+ return (event) => {
45
+ const l = getLeft(event);
46
+ const r = getRight(event);
47
+ if (l == null || r == null)
48
+ return false;
49
+ return l === r;
50
+ };
51
+ case "!=":
52
+ return (event) => {
53
+ const l = getLeft(event);
54
+ const r = getRight(event);
55
+ if (l == null || r == null)
56
+ return false;
57
+ return l !== r;
58
+ };
59
+ case "<":
60
+ case "<=":
61
+ case ">":
62
+ case ">=":
63
+ return (event) => {
64
+ let l = getLeft(event);
65
+ let r = getRight(event);
66
+ if (l == null || r == null)
67
+ return false;
68
+ if (rankFn != null) {
69
+ l = rankFn(String(l));
70
+ r = rankFn(String(r));
71
+ if (l == null || r == null)
72
+ return false;
73
+ }
74
+ return compareOrdered(l, op, r);
75
+ };
76
+ case "~": {
77
+ // 右辺が文字列リテラルならコンパイル時に RegExp をキャッシュ
78
+ const cachedRegex = right.kind === "string" ? new RegExp(right.value) : null;
79
+ return (event) => {
80
+ const l = getLeft(event);
81
+ if (l == null)
82
+ return false;
83
+ const re = cachedRegex ?? (() => {
84
+ const r = getRight(event);
85
+ if (r == null)
86
+ return null;
87
+ try {
88
+ return new RegExp(String(r));
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ })();
94
+ if (re == null)
95
+ return false;
96
+ return re.test(String(l));
97
+ };
98
+ }
99
+ case "!~": {
100
+ const cachedRegexNeg = right.kind === "string" ? new RegExp(right.value) : null;
101
+ return (event) => {
102
+ const l = getLeft(event);
103
+ if (l == null)
104
+ return false;
105
+ const re = cachedRegexNeg ?? (() => {
106
+ const r = getRight(event);
107
+ if (r == null)
108
+ return null;
109
+ try {
110
+ return new RegExp(String(r));
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ })();
116
+ if (re == null)
117
+ return false;
118
+ return !re.test(String(l));
119
+ };
120
+ }
121
+ case "in":
122
+ return (event) => {
123
+ const l = getLeft(event);
124
+ const r = getRight(event);
125
+ if (l == null || r == null)
126
+ return false;
127
+ if (Array.isArray(r))
128
+ return r.includes(l);
129
+ return false;
130
+ };
131
+ case "contains":
132
+ return (event) => {
133
+ const l = getLeft(event);
134
+ const r = getRight(event);
135
+ if (l == null || r == null)
136
+ return false;
137
+ if (Array.isArray(l))
138
+ return l.includes(r);
139
+ if (typeof l === "string" && typeof r === "string")
140
+ return l.includes(r);
141
+ return false;
142
+ };
143
+ }
144
+ }
145
+ function makeGetter(node) {
146
+ switch (node.kind) {
147
+ case "path": {
148
+ const field = (0, field_registry_1.resolveField)(node.segments.join("."));
149
+ if (field == null)
150
+ return () => null;
151
+ return (event) => field.get(event);
152
+ }
153
+ case "string":
154
+ return () => node.value;
155
+ case "number":
156
+ return () => node.value;
157
+ case "boolean":
158
+ return () => node.value;
159
+ case "null":
160
+ return () => null;
161
+ case "list":
162
+ return () => node.items.map((item) => {
163
+ switch (item.kind) {
164
+ case "string": return item.value;
165
+ case "number": return item.value;
166
+ case "boolean": return item.value;
167
+ default: return null;
168
+ }
169
+ });
170
+ }
171
+ }
172
+ function compareOrdered(l, op, r) {
173
+ switch (op) {
174
+ case "<": return l < r;
175
+ case "<=": return l <= r;
176
+ case ">": return l > r;
177
+ case ">=": return l >= r;
178
+ default: return false;
179
+ }
180
+ }
181
+ function getRankFn(kind) {
182
+ switch (kind) {
183
+ case "enum:frameLevel": return rank_maps_1.toFrameLevelRank;
184
+ case "enum:intensity": return rank_maps_1.toIntensityRank;
185
+ case "enum:lgInt": return rank_maps_1.toLgIntRank;
186
+ default: return null;
187
+ }
188
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterFieldError = exports.FilterTypeError = exports.FilterSyntaxError = void 0;
4
+ class FilterSyntaxError extends Error {
5
+ source;
6
+ position;
7
+ constructor(source, position, message) {
8
+ super(message);
9
+ this.source = source;
10
+ this.position = position;
11
+ this.name = "FilterSyntaxError";
12
+ }
13
+ /** 位置付きフォーマット済みエラー表示 */
14
+ format() {
15
+ const pointer = " ".repeat(this.position) + "^";
16
+ return `フィルタ構文エラー: ${this.position + 1}文字目${this.message}\n${this.source}\n${pointer}`;
17
+ }
18
+ }
19
+ exports.FilterSyntaxError = FilterSyntaxError;
20
+ class FilterTypeError extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "FilterTypeError";
24
+ }
25
+ }
26
+ exports.FilterTypeError = FilterTypeError;
27
+ class FilterFieldError extends Error {
28
+ fieldName;
29
+ availableFields;
30
+ constructor(fieldName, availableFields) {
31
+ super(`未知のフィールド: ${fieldName}`);
32
+ this.fieldName = fieldName;
33
+ this.availableFields = availableFields;
34
+ this.name = "FilterFieldError";
35
+ }
36
+ format() {
37
+ const examples = this.availableFields.slice(0, 6).join(", ");
38
+ return `未知のフィールド: ${this.fieldName}\n使える例: ${examples}`;
39
+ }
40
+ }
41
+ exports.FilterFieldError = FilterFieldError;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FILTER_FIELDS = void 0;
4
+ exports.resolveField = resolveField;
5
+ exports.fieldNames = fieldNames;
6
+ function field(kind, aliases, get, supportsOrder) {
7
+ return { kind, aliases, get, supportsOrder };
8
+ }
9
+ /** depth 文字列 "10km" → 数値 10 */
10
+ function parseDepth(d) {
11
+ if (d == null)
12
+ return null;
13
+ const m = d.match(/^(\d+)/);
14
+ return m ? Number(m[1]) : null;
15
+ }
16
+ /** magnitude 文字列 → 数値 */
17
+ function parseMagnitude(m) {
18
+ if (m == null)
19
+ return null;
20
+ const n = Number(m);
21
+ return Number.isFinite(n) ? n : null;
22
+ }
23
+ exports.FILTER_FIELDS = {
24
+ // 識別
25
+ domain: field("string", [], (e) => e.domain),
26
+ type: field("string", ["headType"], (e) => e.type),
27
+ subType: field("string", [], (e) => e.subType),
28
+ classification: field("string", [], (e) => e.classification),
29
+ id: field("string", [], (e) => e.id),
30
+ infoType: field("string", [], (e) => e.infoType),
31
+ // レベル
32
+ frameLevel: field("enum:frameLevel", ["level"], (e) => e.frameLevel, true),
33
+ // 状態フラグ
34
+ isCancellation: field("boolean", ["isCancelled"], (e) => e.isCancellation),
35
+ isWarning: field("boolean", [], (e) => e.isWarning),
36
+ isFinal: field("boolean", [], (e) => e.isFinal),
37
+ isTest: field("boolean", [], (e) => e.isTest),
38
+ isRenotification: field("boolean", [], (e) => e.isRenotification),
39
+ // イベント追跡
40
+ eventId: field("string", [], (e) => e.eventId),
41
+ serial: field("string", [], (e) => e.serial),
42
+ volcanoCode: field("string", [], (e) => e.volcanoCode),
43
+ volcanoName: field("string", [], (e) => e.volcanoName),
44
+ // 震源情報
45
+ hypocenterName: field("string", ["hypocenter"], (e) => e.hypocenterName),
46
+ depth: field("number", [], (e) => parseDepth(e.depth), true),
47
+ magnitude: field("number", ["mag"], (e) => parseMagnitude(e.magnitude), true),
48
+ // 強度
49
+ maxInt: field("enum:intensity", [], (e) => e.maxInt, true),
50
+ maxLgInt: field("enum:lgInt", [], (e) => e.maxLgInt, true),
51
+ forecastMaxInt: field("enum:intensity", [], (e) => e.forecastMaxInt, true),
52
+ alertLevel: field("number", [], (e) => e.alertLevel, true),
53
+ // テキスト
54
+ title: field("string", [], (e) => e.title),
55
+ headline: field("string", [], (e) => e.headline),
56
+ // 地域集約
57
+ areaNames: field("string[]", [], (e) => e.areaNames),
58
+ forecastAreaNames: field("string[]", [], (e) => e.forecastAreaNames),
59
+ municipalityNames: field("string[]", [], (e) => e.municipalityNames),
60
+ observationNames: field("string[]", [], (e) => e.observationNames),
61
+ areaCount: field("number", [], (e) => e.areaCount),
62
+ // 津波
63
+ tsunamiKinds: field("string[]", [], (e) => e.tsunamiKinds),
64
+ };
65
+ /** フィールド名 or エイリアスから FilterField を解決する */
66
+ function resolveField(name) {
67
+ if (name in exports.FILTER_FIELDS)
68
+ return exports.FILTER_FIELDS[name];
69
+ for (const [, f] of Object.entries(exports.FILTER_FIELDS)) {
70
+ if (f.aliases.includes(name))
71
+ return f;
72
+ }
73
+ return null;
74
+ }
75
+ /** 公開フィールド名一覧 (エラーメッセージ用) */
76
+ function fieldNames() {
77
+ return Object.keys(exports.FILTER_FIELDS);
78
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fieldNames = exports.resolveField = exports.FilterFieldError = exports.FilterTypeError = exports.FilterSyntaxError = exports.compileFilter = void 0;
4
+ // 公開 API
5
+ var compile_filter_1 = require("./compile-filter");
6
+ Object.defineProperty(exports, "compileFilter", { enumerable: true, get: function () { return compile_filter_1.compileFilter; } });
7
+ // エラー
8
+ var errors_1 = require("./errors");
9
+ Object.defineProperty(exports, "FilterSyntaxError", { enumerable: true, get: function () { return errors_1.FilterSyntaxError; } });
10
+ Object.defineProperty(exports, "FilterTypeError", { enumerable: true, get: function () { return errors_1.FilterTypeError; } });
11
+ Object.defineProperty(exports, "FilterFieldError", { enumerable: true, get: function () { return errors_1.FilterFieldError; } });
12
+ // フィールドユーティリティ
13
+ var field_registry_1 = require("./field-registry");
14
+ Object.defineProperty(exports, "resolveField", { enumerable: true, get: function () { return field_registry_1.resolveField; } });
15
+ Object.defineProperty(exports, "fieldNames", { enumerable: true, get: function () { return field_registry_1.fieldNames; } });
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parse = parse;
4
+ const errors_1 = require("./errors");
5
+ const MAX_DEPTH = 32;
6
+ class Parser {
7
+ tokens;
8
+ source;
9
+ pos = 0;
10
+ depth = 0;
11
+ constructor(tokens, source) {
12
+ this.tokens = tokens;
13
+ this.source = source;
14
+ }
15
+ parse() {
16
+ const ast = this.parseOr();
17
+ if (this.current().kind !== "eof") {
18
+ throw new errors_1.FilterSyntaxError(this.source, this.current().pos, "で予期しないトークン");
19
+ }
20
+ return ast;
21
+ }
22
+ parseOr() {
23
+ const children = [this.parseAnd()];
24
+ while (this.current().kind === "or") {
25
+ this.advance();
26
+ children.push(this.parseAnd());
27
+ }
28
+ return children.length === 1 ? children[0] : { kind: "or", children };
29
+ }
30
+ parseAnd() {
31
+ const children = [this.parseUnary()];
32
+ while (this.current().kind === "and") {
33
+ this.advance();
34
+ children.push(this.parseUnary());
35
+ }
36
+ return children.length === 1 ? children[0] : { kind: "and", children };
37
+ }
38
+ parseUnary() {
39
+ if (this.current().kind === "not") {
40
+ this.advance();
41
+ this.enterDepth();
42
+ const operand = this.parseUnary();
43
+ this.leaveDepth();
44
+ return { kind: "not", operand };
45
+ }
46
+ return this.parsePrimary();
47
+ }
48
+ parsePrimary() {
49
+ if (this.current().kind === "lparen") {
50
+ this.advance();
51
+ this.enterDepth();
52
+ const expr = this.parseOr();
53
+ this.leaveDepth();
54
+ this.expect("rparen", "で閉じ括弧 ')' が必要");
55
+ return expr;
56
+ }
57
+ const left = this.parseValue();
58
+ const op = this.tryCompOp();
59
+ if (op == null) {
60
+ return { kind: "truthy", value: left };
61
+ }
62
+ const right = this.parseValue();
63
+ return { kind: "comparison", left, op, right };
64
+ }
65
+ parseValue() {
66
+ const token = this.current();
67
+ switch (token.kind) {
68
+ case "ident":
69
+ this.advance();
70
+ return { kind: "path", segments: token.value.split("."), pos: token.pos };
71
+ case "string":
72
+ this.advance();
73
+ return { kind: "string", value: token.value, pos: token.pos };
74
+ case "number":
75
+ this.advance();
76
+ return { kind: "number", value: Number(token.value), pos: token.pos };
77
+ case "boolean":
78
+ this.advance();
79
+ return { kind: "boolean", value: token.value === "true", pos: token.pos };
80
+ case "null":
81
+ this.advance();
82
+ return { kind: "null", pos: token.pos };
83
+ case "lbracket":
84
+ return this.parseList();
85
+ default:
86
+ throw new errors_1.FilterSyntaxError(this.source, token.pos, "で値が必要");
87
+ }
88
+ }
89
+ parseList() {
90
+ const start = this.current().pos;
91
+ this.expect("lbracket", "で '[' が必要");
92
+ const items = [];
93
+ while (this.current().kind !== "rbracket") {
94
+ if (items.length > 0) {
95
+ this.expect("comma", "で ',' が必要");
96
+ }
97
+ items.push(this.parseValue());
98
+ }
99
+ this.expect("rbracket", "で ']' が必要");
100
+ return { kind: "list", items, pos: start };
101
+ }
102
+ tryCompOp() {
103
+ const token = this.current();
104
+ if (token.kind === "op") {
105
+ this.advance();
106
+ return token.value;
107
+ }
108
+ return null;
109
+ }
110
+ current() {
111
+ return this.tokens[this.pos];
112
+ }
113
+ advance() {
114
+ const token = this.tokens[this.pos];
115
+ if (this.pos < this.tokens.length - 1)
116
+ this.pos++;
117
+ return token;
118
+ }
119
+ expect(kind, errorMsg) {
120
+ if (this.current().kind !== kind) {
121
+ throw new errors_1.FilterSyntaxError(this.source, this.current().pos, errorMsg);
122
+ }
123
+ this.advance();
124
+ }
125
+ enterDepth() {
126
+ this.depth++;
127
+ if (this.depth > MAX_DEPTH) {
128
+ throw new errors_1.FilterSyntaxError(this.source, this.current().pos, `でネストが深すぎる (最大 ${MAX_DEPTH} 段)`);
129
+ }
130
+ }
131
+ leaveDepth() {
132
+ this.depth--;
133
+ }
134
+ }
135
+ function parse(tokens, source) {
136
+ return new Parser(tokens, source).parse();
137
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LG_INT_RANK = exports.INTENSITY_RANK = exports.FRAME_LEVEL_RANK = void 0;
4
+ exports.toFrameLevelRank = toFrameLevelRank;
5
+ exports.toIntensityRank = toIntensityRank;
6
+ exports.toLgIntRank = toLgIntRank;
7
+ /** frameLevel → 数値ランク (順序比較用) */
8
+ exports.FRAME_LEVEL_RANK = {
9
+ cancel: 0,
10
+ info: 1,
11
+ normal: 2,
12
+ warning: 3,
13
+ critical: 4,
14
+ };
15
+ /** 震度文字列 → 数値ランク (順序比較用) */
16
+ exports.INTENSITY_RANK = {
17
+ "1": 1, "2": 2, "3": 3, "4": 4,
18
+ "5-": 5, "5弱": 5, "5+": 6, "5強": 6,
19
+ "6-": 7, "6弱": 7, "6+": 8, "6強": 8, "7": 9,
20
+ };
21
+ /** 長周期地震動階級 → 数値ランク */
22
+ exports.LG_INT_RANK = {
23
+ "0": 0, "1": 1, "2": 2, "3": 3, "4": 4,
24
+ };
25
+ /** 文字列からランク値を返す。未知の値は null */
26
+ function toFrameLevelRank(value) {
27
+ return exports.FRAME_LEVEL_RANK[value] ?? null;
28
+ }
29
+ function toIntensityRank(value) {
30
+ return exports.INTENSITY_RANK[value.replace(/\s+/g, "")] ?? null;
31
+ }
32
+ function toLgIntRank(value) {
33
+ return exports.LG_INT_RANK[value] ?? null;
34
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tokenize = tokenize;
4
+ const errors_1 = require("./errors");
5
+ const KEYWORDS = {
6
+ and: "and",
7
+ or: "or",
8
+ not: "not",
9
+ true: "boolean",
10
+ false: "boolean",
11
+ null: "null",
12
+ in: "op",
13
+ contains: "op",
14
+ };
15
+ const OPERATORS = ["!=", "<=", ">=", "!~", "=", "<", ">", "~"];
16
+ function tokenize(source) {
17
+ const tokens = [];
18
+ let i = 0;
19
+ while (i < source.length) {
20
+ // 空白スキップ
21
+ if (/\s/.test(source[i])) {
22
+ i++;
23
+ continue;
24
+ }
25
+ // 文字列リテラル
26
+ if (source[i] === '"' || source[i] === "'") {
27
+ const quote = source[i];
28
+ const start = i;
29
+ i++; // 開き引用符
30
+ let value = "";
31
+ while (i < source.length && source[i] !== quote) {
32
+ if (source[i] === "\\" && i + 1 < source.length) {
33
+ i++;
34
+ value += source[i];
35
+ }
36
+ else {
37
+ value += source[i];
38
+ }
39
+ i++;
40
+ }
41
+ if (i >= source.length) {
42
+ throw new errors_1.FilterSyntaxError(source, start, "で文字列が閉じられていない");
43
+ }
44
+ i++; // 閉じ引用符
45
+ tokens.push({ kind: "string", value, pos: start });
46
+ continue;
47
+ }
48
+ // 数値リテラル
49
+ if (/[0-9]/.test(source[i]) || (source[i] === "-" && i + 1 < source.length && /[0-9]/.test(source[i + 1]))) {
50
+ const start = i;
51
+ if (source[i] === "-")
52
+ i++;
53
+ while (i < source.length && /[0-9]/.test(source[i]))
54
+ i++;
55
+ if (i < source.length && source[i] === ".") {
56
+ i++;
57
+ while (i < source.length && /[0-9]/.test(source[i]))
58
+ i++;
59
+ }
60
+ tokens.push({ kind: "number", value: source.slice(start, i), pos: start });
61
+ continue;
62
+ }
63
+ // 括弧・ブラケット・カンマ
64
+ if (source[i] === "(") {
65
+ tokens.push({ kind: "lparen", value: "(", pos: i });
66
+ i++;
67
+ continue;
68
+ }
69
+ if (source[i] === ")") {
70
+ tokens.push({ kind: "rparen", value: ")", pos: i });
71
+ i++;
72
+ continue;
73
+ }
74
+ if (source[i] === "[") {
75
+ tokens.push({ kind: "lbracket", value: "[", pos: i });
76
+ i++;
77
+ continue;
78
+ }
79
+ if (source[i] === "]") {
80
+ tokens.push({ kind: "rbracket", value: "]", pos: i });
81
+ i++;
82
+ continue;
83
+ }
84
+ if (source[i] === ",") {
85
+ tokens.push({ kind: "comma", value: ",", pos: i });
86
+ i++;
87
+ continue;
88
+ }
89
+ // 演算子
90
+ let matchedOp = null;
91
+ for (const op of OPERATORS) {
92
+ if (source.startsWith(op, i)) {
93
+ matchedOp = op;
94
+ break;
95
+ }
96
+ }
97
+ if (matchedOp != null) {
98
+ tokens.push({ kind: "op", value: matchedOp, pos: i });
99
+ i += matchedOp.length;
100
+ continue;
101
+ }
102
+ // 識別子 / キーワード (dot パス含む)
103
+ if (/[a-zA-Z_]/.test(source[i])) {
104
+ const start = i;
105
+ while (i < source.length && /[a-zA-Z0-9_.]/.test(source[i]))
106
+ i++;
107
+ const word = source.slice(start, i);
108
+ const keyword = KEYWORDS[word];
109
+ if (keyword != null) {
110
+ tokens.push({ kind: keyword, value: word, pos: start });
111
+ }
112
+ else {
113
+ tokens.push({ kind: "ident", value: word, pos: start });
114
+ }
115
+ continue;
116
+ }
117
+ throw new errors_1.FilterSyntaxError(source, i, `で予期しない文字 '${source[i]}'`);
118
+ }
119
+ tokens.push({ kind: "eof", value: "", pos: source.length });
120
+ return tokens;
121
+ }