Coverage for src / competitive_verifier / oj / languages / nim.py: 28%

72 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-04-26 12:38 +0900

1# Python Version: 3.x 

2import functools 

3import pathlib 

4from collections.abc import Generator 

5from logging import getLogger 

6 

7from pydantic import BaseModel, Field 

8 

9from competitive_verifier.util import read_text_normalized 

10 

11from .base import Language, LanguageEnvironment, OjVerifyLanguageConfig 

12 

13logger = getLogger(__name__) 

14 

15DEFAULT_COMPILE_TO = "cpp" 

16DEFAULT_NIM_FLAGS = ["-d:release", "--opt:speed"] 

17 

18 

19class OjVerifyNimConfigEnv(BaseModel): 

20 compile_to: str | None 

21 NIMFLAGS: list[str] | None 

22 

23 

24class OjVerifyNimConfig(OjVerifyLanguageConfig): 

25 environments: list[OjVerifyNimConfigEnv] = Field( 

26 default_factory=list[OjVerifyNimConfigEnv] 

27 ) 

28 

29 

30class NimLanguageEnvironment(LanguageEnvironment): 

31 compile_to: str 

32 nim_flags: list[str] 

33 

34 def __init__(self, *, compile_to: str, nim_flags: list[str]): 

35 self.compile_to = compile_to 

36 self.nim_flags = nim_flags 

37 

38 @property 

39 def name(self) -> str: 

40 return "Nim" 

41 

42 def get_compile_command( 

43 self, path: pathlib.Path, *, basedir: pathlib.Path, tempdir: pathlib.Path 

44 ) -> list[str]: 

45 return [ 

46 "nim", 

47 self.compile_to, 

48 "-p:.", 

49 f"-o:{tempdir / 'a.out'!s}", 

50 f"--nimcache:{tempdir!s}", 

51 *self.nim_flags, 

52 str(path), 

53 ] 

54 

55 def get_execute_command( 

56 self, path: pathlib.Path, *, basedir: pathlib.Path, tempdir: pathlib.Path 

57 ) -> str: 

58 return str(tempdir / "a.out") 

59 

60 

61def _parse_path(lines: list[str]) -> Generator[str, None, None]: 

62 for line in lines: 

63 l = line.strip() 

64 if l.startswith("include"): 

65 yield from l[7:].strip().split(",") 

66 elif l.startswith("import"): 

67 l = l[6:] 

68 i = l.find(" except ") 

69 if i >= 0: 

70 l = l[:i] 

71 yield from l.split(",") 

72 elif l.startswith("from"): 

73 i = l.find(" import ") 

74 if i >= 0: 

75 yield l[4 : i - 1] 

76 

77 

78def _unquote_path(p: str) -> pathlib.Path: 

79 p = p.strip() 

80 if p.startswith('"'): 

81 p = p[1 : len(p) - 1] 

82 else: 

83 p += ".nim" 

84 return pathlib.Path(p) 

85 

86 

87@functools.cache 

88def _list_direct_dependencies( 

89 path: pathlib.Path, 

90 *, 

91 basedir: pathlib.Path, 

92) -> list[pathlib.Path]: 

93 dependencies = [path.resolve()] 

94 for item in _parse_path(read_text_normalized(basedir / path).splitlines()): 

95 item_ = _unquote_path(item) 

96 if item_.exists(): 

97 dependencies.append(item_) 

98 return list(set(dependencies)) 

99 

100 

101class NimLanguage(Language): 

102 config: OjVerifyNimConfig = Field(default_factory=OjVerifyNimConfig) 

103 

104 def list_dependencies( 

105 self, path: pathlib.Path, *, basedir: pathlib.Path 

106 ) -> list[pathlib.Path]: 

107 dependencies: list[pathlib.Path] = [] 

108 visited: set[pathlib.Path] = set() 

109 stk = [path.resolve()] 

110 while stk: 

111 path = stk.pop() 

112 if path in visited: 

113 continue 

114 visited.add(path) 

115 for child in _list_direct_dependencies(path, basedir=basedir): 

116 dependencies.append(child) 

117 stk.append(child) 

118 return list(set(dependencies)) 

119 

120 def list_environments( 

121 self, path: pathlib.Path, *, basedir: pathlib.Path 

122 ) -> list[NimLanguageEnvironment]: 

123 if self.config.environments: 

124 return [ 

125 NimLanguageEnvironment( 

126 compile_to=DEFAULT_COMPILE_TO, 

127 nim_flags=DEFAULT_NIM_FLAGS, 

128 ) 

129 ] 

130 return [ 

131 NimLanguageEnvironment( 

132 compile_to=env.compile_to or DEFAULT_COMPILE_TO, 

133 nim_flags=env.NIMFLAGS or DEFAULT_NIM_FLAGS, 

134 ) 

135 for env in self.config.environments 

136 ]