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

73 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-03-05 16:00 +0000

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.oj.verify.models import ( 

10 Language, 

11 LanguageEnvironment, 

12 OjVerifyLanguageConfig, 

13) 

14from competitive_verifier.util import read_text_normalized 

15 

16logger = getLogger(__name__) 

17 

18DEFAULT_COMPILE_TO = "cpp" 

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

20 

21 

22class OjVerifyNimConfigEnv(BaseModel): 

23 compile_to: str | None 

24 NIMFLAGS: list[str] | None 

25 

26 

27class OjVerifyNimConfig(OjVerifyLanguageConfig): 

28 environments: list[OjVerifyNimConfigEnv] = Field( 

29 default_factory=list[OjVerifyNimConfigEnv] 

30 ) 

31 

32 

33class NimLanguageEnvironment(LanguageEnvironment): 

34 compile_to: str 

35 nim_flags: list[str] 

36 

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

38 self.compile_to = compile_to 

39 self.nim_flags = nim_flags 

40 

41 @property 

42 def name(self) -> str: 

43 return "Nim" 

44 

45 def get_compile_command( 

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

47 ) -> list[str]: 

48 return [ 

49 "nim", 

50 self.compile_to, 

51 "-p:.", 

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

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

54 *self.nim_flags, 

55 str(path), 

56 ] 

57 

58 def get_execute_command( 

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

60 ) -> str: 

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

62 

63 

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

65 for line in lines: 

66 l = line.strip() 

67 if l.startswith("include"): 

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

69 elif l.startswith("import"): 

70 l = l[6:] 

71 i = l.find(" except ") 

72 if i >= 0: 

73 l = l[:i] 

74 yield from l.split(",") 

75 elif l.startswith("from"): 

76 i = l.find(" import ") 

77 if i >= 0: 

78 yield l[4 : i - 1] 

79 

80 

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

82 p = p.strip() 

83 if p.startswith('"'): 

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

85 else: 

86 p += ".nim" 

87 return pathlib.Path(p) 

88 

89 

90@functools.cache 

91def _list_direct_dependencies( 

92 path: pathlib.Path, 

93 *, 

94 basedir: pathlib.Path, 

95) -> list[pathlib.Path]: 

96 dependencies = [path.resolve()] 

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

98 item_ = _unquote_path(item) 

99 if item_.exists(): 

100 dependencies.append(item_) 

101 return list(set(dependencies)) 

102 

103 

104class NimLanguage(Language): 

105 config: OjVerifyNimConfig 

106 

107 def __init__(self, *, config: OjVerifyNimConfig | None): 

108 self.config = config or OjVerifyNimConfig() 

109 

110 def list_dependencies( 

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

112 ) -> list[pathlib.Path]: 

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

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

115 stk = [path.resolve()] 

116 while stk: 

117 path = stk.pop() 

118 if path in visited: 

119 continue 

120 visited.add(path) 

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

122 dependencies.append(child) 

123 stk.append(child) 

124 return list(set(dependencies)) 

125 

126 def list_environments( 

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

128 ) -> list[NimLanguageEnvironment]: 

129 if self.config.environments: 

130 return [ 

131 NimLanguageEnvironment( 

132 compile_to=DEFAULT_COMPILE_TO, 

133 nim_flags=DEFAULT_NIM_FLAGS, 

134 ) 

135 ] 

136 return [ 

137 NimLanguageEnvironment( 

138 compile_to=env.compile_to or DEFAULT_COMPILE_TO, 

139 nim_flags=env.NIMFLAGS or DEFAULT_NIM_FLAGS, 

140 ) 

141 for env in self.config.environments 

142 ]