-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript_engine.py
More file actions
1464 lines (1270 loc) · 64.8 KB
/
script_engine.py
File metadata and controls
1464 lines (1270 loc) · 64.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
スクリプト実行エンジン
パースされたASTを実行する
"""
from typing import Any, Dict, Optional, Union, List
import itertools
try:
from .script_parser import ScriptParser, ASTNode
from .builtin_functions import (
get_builtin_function,
is_builtin_function,
get_function_usage,
BuiltinFunctions,
BUILTIN_FUNCTIONS
)
from .locales import get_message
except ImportError:
from script_parser import ScriptParser, ASTNode
from builtin_functions import (
get_builtin_function,
is_builtin_function,
get_function_usage,
BuiltinFunctions,
BUILTIN_FUNCTIONS
)
from locales import get_message
# ======================================================================
# EXIT statement exception classes
# ======================================================================
class ControlFlowExit(Exception):
"""
制御フロー脱出の基底クラス
Base class for control flow exit (EXIT FUNCTION/FOR/WHILE)
"""
pass
class FunctionExit(ControlFlowExit):
"""
EXIT FUNCTION用例外クラス
Exception for EXIT FUNCTION statement
"""
pass
class LoopExit(ControlFlowExit):
"""
EXIT FOR / EXIT WHILE用例外クラス
Exception for EXIT FOR and EXIT WHILE statements
Attributes:
loop_type: "FOR" or "WHILE"
"""
def __init__(self, loop_type: str):
self.loop_type = loop_type.upper()
super().__init__()
# ======================================================================
# Script Engine
# ======================================================================
class ScriptEngine:
"""VBA風スクリプトの実行エンジン"""
def __init__(self, locale: str = 'ja'):
self.locale = locale # デフォルトで日本語
self.variables: Dict[str, Any] = {}
self.arrays: Dict[str, Dict[int, Any]] = {}
self.user_functions: Dict[str, Any] = {} # ユーザー定義関数を保持
self.call_stack: List[Dict[str, Any]] = [] # 関数呼び出しスタック
self.max_call_depth = 100 # 最大呼び出し深度(無限再帰防止)
self.parser = ScriptParser(locale=self.locale)
self.print_stack: List[str] = [] # PRINT関数の出力スタック
self.return1_assigned: bool = False # RETURN1が代入されたか
self.return2_assigned: bool = False # RETURN2が代入されたか
# RELAY_OUTPUT制御用フラグと値(Tier 3実装)
self.relay_output_assigned: bool = False # RELAY_OUTPUTが代入されたか
self.relay_output_value: Any = None # RELAY_OUTPUTの値
# ループ制御設定(LOOP_SUBGRAPH用) - チャネル別辞書に変更
# 構造: {"RETURN1": {"enabled": True, "count": 5}, "RETURN2": {...}, ...}
self.loop_config: Dict[str, Dict[str, Any]] = {}
def reset(self):
"""エンジンの状態をリセット"""
self.variables.clear()
self.arrays.clear()
self.user_functions.clear()
self.call_stack.clear()
self.print_stack.clear()
self.return1_assigned = False
self.return2_assigned = False
def set_variable(self, name: str, value: Any):
"""変数を設定(スコープ対応)"""
name_upper = name.upper()
# RETURNとRETURN1の両方でreturn1_assignedを設定(後方互換性)
if name_upper in ('RETURN', 'RETURN1'):
self.return1_assigned = True
# RETURNに代入された値をRETURN1にも反映
if name_upper == 'RETURN':
self.variables['RETURN1'] = value
# RETURN1に代入された値をRETURNにも反映
elif name_upper == 'RETURN1':
self.variables['RETURN'] = value
elif name_upper == 'RETURN2':
self.return2_assigned = True
# 値がリストの場合は辞書型配列に変換
if isinstance(value, list):
array_dict = {i: v for i, v in enumerate(value)}
# 現在のスコープを取得
current_scope = self.get_current_scope()
if current_scope:
current_scope['arrays'][name_upper] = array_dict
else:
self.arrays[name_upper] = array_dict
return
# 値が配列オブジェクトの場合は配列として設定
if isinstance(value, dict) and all(isinstance(k, int) for k in value.keys()):
# 現在のスコープを取得
current_scope = self.get_current_scope()
if current_scope:
current_scope['arrays'][name_upper] = value
else:
self.arrays[name_upper] = value
return
# 現在のスコープを取得
current_scope = self.get_current_scope()
if current_scope:
# 関数内の場合、関数名への代入をチェック(戻り値設定)
if name_upper == current_scope['function_name'].upper():
current_scope['return_value'] = value
# ローカルスコープに設定
current_scope['variables'][name_upper] = value
else:
# グローバルスコープに設定
self.variables[name_upper] = value
def get_variable(self, name: str, default: Any = None) -> Any:
"""変数を取得(スコープ対応)"""
name_upper = name.upper()
# 特殊変数(RETURN, VAL1, VAL2, TXT1, TXT2)は常にグローバルスコープから取得
# これにより、PRINT関数内で特殊変数を参照した際に正しい値が取得できる
special_vars = ['RETURN', 'VAL1', 'VAL2', 'VAL_1', 'VAL_2', 'TXT1', 'TXT2']
if name_upper in special_vars:
value = self.variables.get(name_upper, default if default is not None else 0)
return value
# 現在のスコープから検索
current_scope = self.get_current_scope()
if current_scope:
# ローカル変数をチェック
if name_upper in current_scope['variables']:
return current_scope['variables'][name_upper]
# パラメータをチェック
if name_upper in current_scope['parameters']:
return current_scope['parameters'][name_upper]
# 関数名(戻り値)をチェック
if name_upper == current_scope['function_name'].upper():
return current_scope.get('return_value', 0)
# グローバル変数をチェック(関数内からもアクセス可能)
return self.variables.get(name_upper, default)
def get_print_output(self) -> List[str]:
"""PRINT関数の出力スタックを取得"""
return self.print_stack
def clear_print_stack(self):
"""PRINT関数の出力スタックをクリア"""
self.print_stack.clear()
def add_to_print_stack(self, message: str):
"""PRINT関数の出力をスタックに追加"""
self.print_stack.append(message)
def push_scope(self, function_name: str, parameters: Dict[str, Any], parameter_arrays: Dict[str, Dict] = None, array_mappings: Dict[str, str] = None):
"""新しいスコープをプッシュ"""
if len(self.call_stack) >= self.max_call_depth:
raise RuntimeError(get_message('error_max_call_depth', self.locale, self.max_call_depth))
scope = {
'function_name': function_name,
'variables': {},
'arrays': parameter_arrays.copy() if parameter_arrays else {},
'parameters': parameters.copy(),
'array_mappings': array_mappings.copy() if array_mappings else {},
'return_value': "" # EasyScripter仕様: 未設定の関数戻り値は空文字列
}
self.call_stack.append(scope)
def pop_scope(self) -> Dict[str, Any]:
"""スコープをポップして戻り値を返す"""
if self.call_stack:
scope = self.call_stack.pop()
# 配列パラメータの変更を元の配列に反映
if 'array_mappings' in scope and scope['array_mappings']:
for param_name, original_name in scope['array_mappings'].items():
if param_name in scope['arrays']:
# 関数内で変更された配列を元の配列に反映
original_name_upper = original_name.upper()
# 現在のスコープ(呼び出し元)を確認
parent_scope = self.get_current_scope()
if parent_scope and original_name_upper in parent_scope['arrays']:
# 親スコープの配列に反映
parent_scope['arrays'][original_name_upper].update(scope['arrays'][param_name])
elif original_name_upper in self.arrays:
# グローバル配列に反映
self.arrays[original_name_upper].update(scope['arrays'][param_name])
else:
# 配列が存在しない場合、新しく作成
if parent_scope:
parent_scope['arrays'][original_name_upper] = scope['arrays'][param_name].copy()
else:
self.arrays[original_name_upper] = scope['arrays'][param_name].copy()
return scope.get('return_value', "") # EasyScripter仕様: 未設定の関数戻り値は空文字列
return "" # EasyScripter仕様: 未設定の関数戻り値は空文字列
def get_current_scope(self) -> Optional[Dict[str, Any]]:
"""現在のスコープを取得"""
return self.call_stack[-1] if self.call_stack else None
def get_array_ubound(self, array_name: str, dimension: int = 1) -> float:
"""
配列の最大インデックスを取得(VBAのUBOUND相当)
Args:
array_name: 配列名
dimension: 次元(1ベース)
Returns:
最大インデックス値(配列が存在しないまたは空の場合は-1)
"""
array_name_upper = array_name.upper()
# 現在のスコープから配列を検索
current_scope = self.get_current_scope()
array_dict = None
if current_scope and array_name_upper in current_scope['arrays']:
array_dict = current_scope['arrays'][array_name_upper]
elif array_name_upper in self.arrays:
array_dict = self.arrays[array_name_upper]
if array_dict:
if not array_dict:
return -1.0 # 空配列
# キーのタイプをチェック
first_key = next(iter(array_dict.keys()))
if isinstance(first_key, tuple):
# 多次元配列
if dimension > len(first_key):
return -1.0
# 指定された次元の最大値を取得
max_index = max(key[dimension - 1] for key in array_dict.keys())
return float(max_index)
else:
# 1次元配列
if dimension == 1:
return float(max(array_dict.keys()))
else:
return -1.0
return -1.0
def get_array_lbound(self, array_name: str, dimension: int = 1) -> float:
"""
配列の最小インデックスを取得(VBAのLBOUND相当)
Args:
array_name: 配列名
dimension: 次元(1ベース)
Returns:
最小インデックス値(配列が存在しないまたは空の場合は0)
"""
array_name_upper = array_name.upper()
# 現在のスコープから配列を検索
current_scope = self.get_current_scope()
array_dict = None
if current_scope and array_name_upper in current_scope['arrays']:
array_dict = current_scope['arrays'][array_name_upper]
elif array_name_upper in self.arrays:
array_dict = self.arrays[array_name_upper]
if array_dict:
if not array_dict:
return 0.0 # 空配列でも0を返す
# キーのタイプをチェック
first_key = next(iter(array_dict.keys()))
if isinstance(first_key, tuple):
# 多次元配列
if dimension > len(first_key):
return 0.0
# 指定された次元の最小値を取得(通常は0)
min_index = min(key[dimension - 1] for key in array_dict.keys())
return float(min_index)
else:
# 1次元配列
if dimension == 1:
return float(min(array_dict.keys()))
else:
return 0.0
return 0.0
def execute_split_function(self, array_name: str, text: Any, delimiter: Any) -> float:
"""SPLIT関数を実行"""
array_name = str(array_name).upper()
text = str(text) if text is not None else ""
delimiter = str(delimiter) if delimiter is not None else ","
# 現在のスコープを取得
current_scope = self.get_current_scope()
# 区切り文字で分割
if delimiter == "":
# 空の区切り文字の場合、文字単位で分割
parts = list(text)
else:
# 通常の区切り文字で分割
parts = text.split(delimiter)
# 配列に格納(0ベースインデックス)
if current_scope:
# 関数内の場合、ローカルスコープに配列を作成
current_scope['arrays'][array_name] = {}
for i, part in enumerate(parts):
current_scope['arrays'][array_name][i] = part
else:
# グローバルスコープに配列を作成
self.arrays[array_name] = {}
for i, part in enumerate(parts):
self.arrays[array_name][i] = part
# 要素数を返す
return float(len(parts))
def execute(self, script: str):
"""スクリプトを実行"""
try:
# スクリプト実行開始時に予約変数の初期値を保存(OUTPUT関数用)
self._reserved_vars_initial = {
'TXT1': self.variables.get('TXT1'),
'TXT2': self.variables.get('TXT2'),
'ANY_INPUT': self.variables.get('ANY_INPUT')
}
ast = self.parser.parse(script)
for statement in ast:
self.execute_statement(statement)
# RETURNまたはRETURN_VALUEの値を返す
return self.variables.get('RETURN', self.variables.get('RETURN_VALUE', None))
except Exception as e:
raise RuntimeError(get_message('error_script_execution', self.locale, str(e)))
def execute_statement(self, node: ASTNode) -> Any:
"""ステートメントを実行"""
if node.type == 'FUNCTION_DEF':
# 関数定義を実行(登録)
return self.execute_function_definition(node)
elif node.type == 'ASSIGN':
# 変数代入(スコープ対応)
value = self.evaluate_expression(node.value)
var_name = node.variable.upper() # 変数名を大文字化
# RELAY_OUTPUT変数への代入を検出(Tier 3実装)
if var_name == "RELAY_OUTPUT":
self.relay_output_assigned = True
self.relay_output_value = value
self.set_variable(node.variable, value)
return value
elif node.type == 'ASSIGN_ARRAY':
# 配列代入(配列名も大文字小文字を区別しない)
array_name = node.array.upper()
index = int(self.evaluate_expression(node.index))
value = self.evaluate_expression(node.value)
# 現在のスコープを取得
current_scope = self.get_current_scope()
if current_scope:
# 関数内の場合、ローカルスコープの配列に設定
if array_name not in current_scope['arrays']:
current_scope['arrays'][array_name] = {}
current_scope['arrays'][array_name][index] = value
else:
# グローバルスコープに設定
if array_name not in self.arrays:
self.arrays[array_name] = {}
self.arrays[array_name][index] = value
return value
elif node.type == 'ASSIGN_ARRAY_MULTI':
# 多次元配列代入
array_name = node.array.upper()
indices = tuple(int(self.evaluate_expression(idx)) for idx in node.indices)
value = self.evaluate_expression(node.value)
# 現在のスコープを取得
current_scope = self.get_current_scope()
if current_scope:
# 関数内の場合、ローカルスコープの配列に設定
if array_name not in current_scope['arrays']:
current_scope['arrays'][array_name] = {}
current_scope['arrays'][array_name][indices] = value
else:
# グローバルスコープに設定
if array_name not in self.arrays:
self.arrays[array_name] = {}
self.arrays[array_name][indices] = value
return value
elif node.type == 'SELECT_CASE':
# SELECT CASE文
return self.execute_select_case(node)
elif node.type == 'IF':
# IF文(ELSEIF対応)
condition = self.evaluate_expression(node.condition)
if self.is_true(condition):
# IF条件が真の場合
for stmt in node.then_branch:
self.execute_statement(stmt)
else:
# ELSEIF条件の順次評価
executed = False
if hasattr(node, 'elseif_branches') and node.elseif_branches:
for elseif_condition, elseif_statements in node.elseif_branches:
if self.is_true(self.evaluate_expression(elseif_condition)):
for stmt in elseif_statements:
self.execute_statement(stmt)
executed = True
break # 最初に真になったELSEIF句のみ実行
# ELSE句の実行(ELSEIF句が実行されなかった場合のみ)
if not executed and node.else_branch:
for stmt in node.else_branch:
self.execute_statement(stmt)
elif node.type == 'WHILE':
# WHILE文
try:
while self.is_true(self.evaluate_expression(node.condition)):
for stmt in node.body:
self.execute_statement(stmt)
except LoopExit as e:
# EXIT WHILE処理
if e.loop_type == 'WHILE':
pass # ループを正常終了
else:
# 他のループタイプ(FOR)のEXITは再スロー
raise
elif node.type == 'FOR':
# FOR文(ループ変数も大文字小文字を区別しない)
var_name = node.variable.upper()
start = self.evaluate_expression(node.start)
end = self.evaluate_expression(node.end)
step = self.evaluate_expression(node.step) if hasattr(node, 'step') else 1
# 数値に変換
start = self.to_number(start)
end = self.to_number(end)
step = self.to_number(step)
# ループ実行
current = start
try:
if step > 0:
while current <= end:
self.set_variable(var_name, current)
for stmt in node.body:
self.execute_statement(stmt)
current += step
else:
while current >= end:
self.set_variable(var_name, current)
for stmt in node.body:
self.execute_statement(stmt)
current += step
except LoopExit as e:
# EXIT FOR処理
if e.loop_type == 'FOR':
pass # ループを正常終了
else:
# 他のループタイプ(WHILE)のEXITは再スロー
raise
elif node.type == 'dim':
# DIM文(配列宣言)
array_name = node.array_name.upper()
if node.sizes:
# 配列として宣言
if len(node.sizes) == 1:
# 1次元配列
size = int(self.evaluate_expression(node.sizes[0]))
# 配列を初期化(0ベースインデックス)
scope = self.get_current_scope()
if scope:
scope['arrays'][array_name] = {i: 0 for i in range(size + 1)}
else:
self.arrays[array_name] = {i: 0 for i in range(size + 1)}
else:
# 多次元配列
sizes = [int(self.evaluate_expression(s)) for s in node.sizes]
# 多次元配列を初期化
scope = self.get_current_scope()
array_dict = {}
# 全ての組み合わせのインデックスを作成
for indices in itertools.product(*[range(s+1) for s in sizes]):
# タプルをキーとして使用
array_dict[indices] = 0
if scope:
scope['arrays'][array_name] = array_dict
else:
self.arrays[array_name] = array_dict
else:
# 通常の変数として宣言
self.set_variable(array_name, 0)
return None
elif node.type == 'REDIM_STMT':
# REDIM文
array_name = node.array_name.upper()
new_size = int(self.evaluate_expression(node.size))
preserve = False
if node.preserve:
preserve = self.is_true(self.evaluate_expression(node.preserve))
self.execute_redim_function(array_name, new_size, preserve)
return new_size
elif node.type == 'ARRAY_STMT':
# ARRAY文
array_name = node.array_name.upper()
values = []
for value_node in node.values:
values.append(self.evaluate_expression(value_node))
self.execute_array_function_with_name(array_name, values)
return len(values)
elif node.type == 'SPLIT_STMT':
# SPLIT文
array_name = node.array_name.upper()
text = str(self.evaluate_expression(node.text))
delimiter = str(self.evaluate_expression(node.delimiter))
return self.execute_split_function(array_name, text, delimiter)
elif node.type == 'FUNCTION_CALL':
# 関数呼び出し(ステートメントとしても扱う)
return self.evaluate_expression(node)
elif node.type == 'RETURN':
# RETURN文の処理
current_scope = self.get_current_scope()
if current_scope:
if hasattr(node, 'value') and node.value:
# RETURN value の形式
return_value = self.evaluate_expression(node.value)
current_scope['return_value'] = return_value
return return_value
else:
# 単純なRETURN(戻り値なし)
current_scope['return_value'] = 0
return 0
else:
# グローバルスコープでのRETURN(RETURN変数として扱う)
if hasattr(node, 'value') and node.value:
return_value = self.evaluate_expression(node.value)
self.variables['RETURN'] = return_value
return return_value
return 0
elif node.type == 'EXIT':
# EXIT文の処理
exit_type = node.exit_type.upper() # "FUNCTION", "FOR", "WHILE"
if exit_type == 'FUNCTION':
raise FunctionExit()
elif exit_type == 'FOR':
raise LoopExit('FOR')
elif exit_type == 'WHILE':
raise LoopExit('WHILE')
else:
raise RuntimeError(f"不明なEXIT文タイプ: {exit_type}")
else:
# その他の式
return self.evaluate_expression(node)
def execute_function_definition(self, node: ASTNode):
"""関数定義を実行(登録)"""
func_name = node.name.upper()
# ビルトイン関数との名前衝突チェック
if is_builtin_function(func_name):
raise RuntimeError(get_message('error_function_conflict', self.locale, node.name))
# 関数を辞書に登録
self.user_functions[func_name] = node
return None
def execute_select_case(self, node: ASTNode) -> Any:
"""SELECT CASE文を実行"""
# テスト式を評価
test_value = self.evaluate_expression(node.test_expression)
# 各Caseを順番に評価
for case in node.cases:
if self.match_case(test_value, case.conditions):
# マッチしたCaseのステートメントを実行
for stmt in case.statements:
self.execute_statement(stmt)
return # 最初にマッチしたCaseで終了
# Case Elseの実行
if node.else_case:
for stmt in node.else_case:
self.execute_statement(stmt)
def match_case(self, test_value: Any, conditions: List[ASTNode]) -> bool:
"""Case条件のマッチング判定"""
for condition in conditions:
if self.match_single_condition(test_value, condition):
return True # いずれかの条件にマッチすればTrue
return False
def match_single_condition(self, test_value: Any, condition: ASTNode) -> bool:
"""単一のCase条件のマッチング判定"""
if condition.type == 'CASE_VALUE':
# 単一値の比較
condition_value = self.evaluate_expression(condition.value)
return self.compare_values(test_value, condition_value, 'EQ')
elif condition.type == 'CASE_RANGE':
# 範囲のチェック (start TO end)
start_value = self.evaluate_expression(condition.start)
end_value = self.evaluate_expression(condition.end)
# 数値比較を試みる
try:
test_num = self.to_number(test_value)
start_num = self.to_number(start_value)
end_num = self.to_number(end_value)
return start_num <= test_num <= end_num
except:
# 文字列比較
test_str = str(test_value).upper()
start_str = str(start_value).upper()
end_str = str(end_value).upper()
return start_str <= test_str <= end_str
elif condition.type == 'CASE_IS':
# Is演算子による比較
compare_value = self.evaluate_expression(condition.value)
return self.compare_values(test_value, compare_value, condition.operator)
else:
# その他の条件(式として評価)
condition_value = self.evaluate_expression(condition)
return self.compare_values(test_value, condition_value, 'EQ')
def compare_values(self, left: Any, right: Any, operator: str) -> bool:
"""値を比較(大文字小文字を区別しない)"""
# 両方が数値として有効な場合のみ数値比較
left_is_numeric = False
right_is_numeric = False
try:
# 数値型かチェック
if isinstance(left, (int, float)):
left_num = float(left)
left_is_numeric = True
elif isinstance(left, str) and left.replace('.', '', 1).replace('-', '', 1).isdigit():
left_num = float(left)
left_is_numeric = True
except:
pass
try:
if isinstance(right, (int, float)):
right_num = float(right)
right_is_numeric = True
elif isinstance(right, str) and right.replace('.', '', 1).replace('-', '', 1).isdigit():
right_num = float(right)
right_is_numeric = True
except:
pass
# 両方が数値の場合は数値比較
if left_is_numeric and right_is_numeric:
if operator == 'EQ':
return left_num == right_num
elif operator == 'NEQ':
return left_num != right_num
elif operator == 'LT':
return left_num < right_num
elif operator == 'GT':
return left_num > right_num
elif operator == 'LTE':
return left_num <= right_num
elif operator == 'GTE':
return left_num >= right_num
# それ以外は文字列比較(大文字小文字を区別しない)
left_str = str(left).upper()
right_str = str(right).upper()
if operator == 'EQ':
return left_str == right_str
elif operator == 'NEQ':
return left_str != right_str
elif operator == 'LT':
return left_str < right_str
elif operator == 'GT':
return left_str > right_str
elif operator == 'LTE':
return left_str <= right_str
elif operator == 'GTE':
return left_str >= right_str
return False
def execute_user_function(self, func_name: str, arguments: List[Any], arg_names: List[str] = None) -> Any:
"""ユーザー定義関数を実行"""
func_name_upper = func_name.upper()
func_def = self.user_functions[func_name_upper]
# 必須パラメータの数をチェック
required_params = [p for p in func_def.parameters if not hasattr(p, 'optional') or not p.optional]
if len(arguments) < len(required_params):
# パラメータ名のリストを作成
param_names = [p.name for p in func_def.parameters]
param_list = ", ".join(param_names) if param_names else ""
error_msg = f"関数 {func_name}: 引数が不足しています(必要: {len(required_params)}, 実際: {len(arguments)})"
usage_msg = f"使用例: {func_name}({param_list})"
raise RuntimeError(f"{error_msg}\n{usage_msg}")
# パラメータの準備
parameters = {}
parameter_arrays = {}
array_mappings = {} # パラメータ名 -> 元の配列名のマッピング
for i, param in enumerate(func_def.parameters):
param_name = param.name.upper()
if i < len(arguments):
# 引数が提供されている場合
arg_value = arguments[i]
# 配列引数の場合、配列として設定
if isinstance(arg_value, dict) and arg_names and i < len(arg_names) and arg_names[i]:
parameter_arrays[param_name] = arg_value.copy() # コピーを作成
array_mappings[param_name] = arg_names[i] # 元の配列名を記録
parameters[param_name] = 0 # 変数としても初期化
else:
parameters[param_name] = arg_value
elif hasattr(param, 'optional') and param.optional and hasattr(param, 'default_value') and param.default_value is not None:
# オプション引数のデフォルト値を評価
parameters[param_name] = self.evaluate_expression(param.default_value)
else:
# デフォルト値がない場合は0
parameters[param_name] = 0
# 新しいスコープでの実行(配列も渡す)
self.push_scope(func_name_upper, parameters, parameter_arrays, array_mappings)
try:
# 関数本体を実行
for statement in func_def.body:
self.execute_statement(statement)
except FunctionExit:
# EXIT FUNCTION処理 - 早期リターン
pass # finally節で戻り値を返すので何もしない
finally:
# スコープをポップして戻り値を返す
return_value = self.pop_scope()
return return_value
def evaluate_expression(self, node: Any) -> Any:
"""式を評価"""
# ASTNodeでない場合(直接の値)
if not isinstance(node, ASTNode):
return node
if node is None:
return 0
if node.type == 'LITERAL':
return node.value
elif node.type == 'VARIABLE':
# 変数名を大文字に統一して取得(スコープ対応)
return self.get_variable(node.name, 0)
elif node.type == 'ARRAY_VAR':
# 配列変数参照(ITEMS[]記法) - 配列オブジェクト自体を返す
array_name = node.name.upper()
# 現在のスコープから配列を検索
current_scope = self.get_current_scope()
if current_scope and array_name in current_scope['arrays']:
return {'_array_ref': array_name, '_scope': 'local', '_data': current_scope['arrays'][array_name]}
# グローバルスコープから検索
if array_name in self.arrays:
return {'_array_ref': array_name, '_scope': 'global', '_data': self.arrays[array_name]}
# 配列が存在しない場合は空の配列オブジェクトを返す
return {'_array_ref': array_name, '_scope': 'local' if current_scope else 'global', '_data': {}}
elif node.type == 'ARRAY_ACCESS':
# 配列名を大文字に統一
array_name = node.array.upper()
index = int(self.evaluate_expression(node.index))
# 現在のスコープから配列を検索
current_scope = self.get_current_scope()
if current_scope and array_name in current_scope['arrays'] and index in current_scope['arrays'][array_name]:
return current_scope['arrays'][array_name][index]
# グローバルスコープから検索
if array_name in self.arrays and index in self.arrays[array_name]:
return self.arrays[array_name][index]
return 0
elif node.type == 'BINARY_OP':
# 短絡評価対応: AND/OR演算子の場合は特別な処理
# VBA準拠: 結果は数値(True=1.0, False=0.0)
if node.operator == 'AND':
left = self.evaluate_expression(node.left)
if not self.is_true(left):
return 0.0 # 左が偽なら右を評価せずに0.0を返す
right = self.evaluate_expression(node.right)
return 1.0 if self.is_true(right) else 0.0
elif node.operator == 'OR':
left = self.evaluate_expression(node.left)
if self.is_true(left):
return 1.0 # 左が真なら右を評価せずに1.0を返す
right = self.evaluate_expression(node.right)
return 1.0 if self.is_true(right) else 0.0
else:
# その他の演算子は通常の評価
left = self.evaluate_expression(node.left)
right = self.evaluate_expression(node.right)
return self.evaluate_binary_op(node.operator, left, right)
elif node.type == 'UNARY_OP':
operand = self.evaluate_expression(node.operand)
return self.evaluate_unary_op(node.operator, operand)
elif node.type == 'FUNCTION_CALL':
# 関数呼び出し
func_name = node.name.upper()
# 配列アクセスかどうかチェック(配列が定義されている場合)
current_scope = self.get_current_scope()
is_array = False
if current_scope and func_name in current_scope['arrays']:
is_array = True
elif func_name in self.arrays:
is_array = True
# 配列アクセスとして処理
if is_array:
if len(node.arguments) == 1:
# 1次元配列アクセス
index = int(self.evaluate_expression(node.arguments[0]))
# 配列から値を取得
if current_scope and func_name in current_scope['arrays'] and index in current_scope['arrays'][func_name]:
return current_scope['arrays'][func_name][index]
elif func_name in self.arrays and index in self.arrays[func_name]:
return self.arrays[func_name][index]
else:
# インデックスが範囲外の場合は0を返す
return 0
else:
# 多次元配列アクセス
indices = tuple(int(self.evaluate_expression(arg)) for arg in node.arguments)
# 配列から値を取得
if current_scope and func_name in current_scope['arrays'] and indices in current_scope['arrays'][func_name]:
return current_scope['arrays'][func_name][indices]
elif func_name in self.arrays and indices in self.arrays[func_name]:
return self.arrays[func_name][indices]
else:
# インデックスが範囲外の場合は0を返す
return 0
# SPLIT関数の特別処理(第1引数は配列名として扱う)
elif func_name == 'SPLIT' and len(node.arguments) >= 3:
# 第1引数は配列変数参照または変数名として扱い、評価しない
array_name_node = node.arguments[0]
if hasattr(array_name_node, 'type'):
if array_name_node.type == 'ARRAY_VAR':
array_name = array_name_node.name
elif array_name_node.type == 'VARIABLE':
array_name = array_name_node.name
elif array_name_node.type == 'LITERAL' and hasattr(array_name_node, 'datatype') and array_name_node.datatype == 'STRING':
array_name = array_name_node.value
else:
array_name = str(self.evaluate_expression(array_name_node))
else:
array_name = str(array_name_node)
# 第2引数以降は通常通り評価
text = self.evaluate_expression(node.arguments[1]) if len(node.arguments) > 1 else ""
delimiter = self.evaluate_expression(node.arguments[2]) if len(node.arguments) > 2 else ","
# SPLIT関数の実行
return self.execute_split_function(array_name, text, delimiter)
# 引数を評価
args = []
arg_names = [] # 配列名を追跡
for arg in node.arguments:
# 配列変数参照の場合(ITEMS[]記法)
if isinstance(arg, ASTNode) and arg.type == 'ARRAY_VAR':
var_name = arg.name.upper()
arg_names.append(var_name)
# 配列の実体を引数として渡す
current_scope = self.get_current_scope()
if current_scope and var_name in current_scope['arrays']:
args.append(current_scope['arrays'][var_name])
elif var_name in self.arrays:
args.append(self.arrays[var_name])
else:
# 配列が存在しない場合は空の配列として渡す
args.append({})
# 変数ノードの場合、配列かどうかをチェック(後方互換性用)
elif isinstance(arg, ASTNode) and arg.type == 'VARIABLE':
var_name = arg.name.upper()
# 配列があるかチェック
current_scope = self.get_current_scope()
if current_scope and var_name in current_scope['arrays']:
# 関数スコープの配列として渡す
arg_names.append(var_name)
args.append(current_scope['arrays'][var_name])
elif var_name in self.arrays:
# グローバル配列として渡す
arg_names.append(var_name)
args.append(self.arrays[var_name])
else:
# 配列でない場合は通常の変数として評価
arg_names.append(None)
args.append(self.evaluate_expression(arg))
else:
args.append(self.evaluate_expression(arg))
arg_names.append(None)
# ユーザー定義関数を最優先でチェック
if func_name in self.user_functions:
return self.execute_user_function(func_name, args, arg_names)
# PRINT関数の特殊処理
elif func_name == 'PRINT':
# 特殊フラグ "CLEAR" のチェック
if len(args) == 1 and str(args[0]).upper() == "CLEAR":
self.clear_print_stack()
return ""
# 引数を文字列に変換して連結(数値のフォーマットを改善)
if args:
formatted_args = []
for arg in args:
if isinstance(arg, float) and arg.is_integer():
# 整数値の場合は .0 を表示しない
formatted_args.append(str(int(arg)))
else:
formatted_args.append(str(arg))
output = " ".join(formatted_args)
else:
output = ""
# スタックに追加
self.add_to_print_stack(output)
return output
# OUTPUT関数の特殊処理(予約変数変換 + locale渡し)
elif func_name == 'OUTPUT':
# 第1引数が文字列リテラルで予約変数名の場合、実値に変換
if len(args) > 0 and isinstance(args[0], str):
reserved_var = args[0].upper()
if reserved_var in ['TXT1', 'TXT2', 'ANY_INPUT']: