Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit 64b8725

Browse files
committed
Implemented Plugin system for the languages
1 parent ea0b9e0 commit 64b8725

File tree

16 files changed

+438
-200
lines changed

16 files changed

+438
-200
lines changed

python/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ find_program(RM_EXE NAMES rm)
1111

1212
# Sources
1313
file(GLOB SOURCES_PY "${CMAKE_CURRENT_SOURCE_DIR}/*.py")
14-
set(PY_MODULES formats tests uis)
14+
set(PY_MODULES formats tests uis languages)
1515
foreach(module_name ${PY_MODULES})
1616
file(GLOB SOURCES_PY_${module_name} "${CMAKE_CURRENT_SOURCE_DIR}/${module_name}/*.py")
1717
endforeach()

python/dependency_finder.py

Lines changed: 0 additions & 71 deletions
This file was deleted.

python/formats/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
from enum import Enum
55
from typing import List, Dict, Optional, Union, Any
66

7-
from task_maker.dependency_finder import Dependency, find_dependency
87
from task_maker.source_file import SourceFile
9-
from task_maker.language import valid_extensions, GraderInfo
10-
from task_maker.language import grader_from_file
8+
from task_maker.languages import GraderInfo, LanguageManager, Dependency
119

1210
VALIDATION_INPUT_NAME = "tm_input_file"
1311

@@ -177,7 +175,7 @@ def list_files(patterns: List[str],
177175
for _file in glob.glob(pattern)] # type: List[str]
178176
return [
179177
res for res in files if res not in exclude
180-
and os.path.splitext(res)[1] in valid_extensions()
178+
and os.path.splitext(res)[1] in LanguageManager.valid_extensions()
181179
]
182180

183181

@@ -231,9 +229,10 @@ def gen_grader_map(graders: List[str], task: Task):
231229

232230
for grader in graders:
233231
name = os.path.basename(grader)
232+
language = LanguageManager.from_file(grader)
234233
info = GraderInfo(
235-
grader_from_file(grader),
236-
[Dependency(name, grader)] + find_dependency(grader))
234+
language,
235+
[Dependency(name, grader)] + language.get_dependencies(grader))
237236
grader_map[info.for_language] = info
238237
task.grader_info.extend([info])
239-
return grader_map
238+
return grader_map

python/language.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

python/languages/__init__.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env python3
2+
3+
import glob
4+
import importlib.util
5+
import os.path
6+
from abc import ABC, abstractmethod
7+
from enum import Enum
8+
from task_maker.args import Arch
9+
from typing import List, Dict
10+
11+
12+
class Dependency:
13+
def __init__(self, name: str, path: str):
14+
self.name = name
15+
self.path = path
16+
17+
def __repr__(self):
18+
return "<Dependency name=%s path=%s>" % (self.name, self.path)
19+
20+
21+
class GraderInfo:
22+
def __init__(self, for_language: "Language", files: List[Dependency]):
23+
self.for_language = for_language
24+
self.files = files
25+
26+
def __repr__(self):
27+
return "<GraderInfo language=%s>" % self.for_language.name
28+
29+
30+
class CommandType(Enum):
31+
LOCAL_FILE = 0
32+
SYSTEM = 1
33+
34+
35+
class Language(ABC):
36+
@property
37+
@abstractmethod
38+
def name(self) -> str:
39+
"""
40+
Unique name of this language
41+
"""
42+
pass
43+
44+
@property
45+
def need_compilation(self) -> bool:
46+
"""
47+
Whether this language needs to be compiled. If set to False
48+
get_compilation_command will never be executed
49+
:return:
50+
"""
51+
return False
52+
53+
@property
54+
@abstractmethod
55+
def source_extensions(self) -> List[str]:
56+
"""
57+
List of extensions (with the dot) of the source files for this language.
58+
The first one is to be considered the principal. The set of extension
59+
of this language must not intersect the one from any other language.
60+
"""
61+
pass
62+
63+
@property
64+
def header_extensions(self) -> List[str]:
65+
"""
66+
List of the extensions (with the dot) of the header files for this
67+
language.
68+
"""
69+
return []
70+
71+
def get_execution_command(self, exe_name: str, args: List[str],
72+
main=None) -> (CommandType, List[str]):
73+
"""
74+
Get the command to use to execute a file from this language
75+
:param exe_name: Name of the (maybe compiled) executable
76+
:param args: Argument to pass to the executable
77+
:param main: Name of the main file, useful for Java
78+
"""
79+
return CommandType.LOCAL_FILE, [exe_name] + args
80+
81+
def get_compilation_command(self, source_filenames: List[str],
82+
exe_name: str, for_evaluation: bool,
83+
target_arch: Arch) -> (CommandType, List[str]):
84+
"""
85+
Get the command to use to compile some files from this language
86+
:param source_filenames: List of files to compile
87+
:param exe_name: Name of the executable to produce
88+
:param for_evaluation: Whether to set the EVAL variable
89+
:param target_arch: Architecture to target the executable on
90+
"""
91+
pass
92+
93+
def get_dependencies(self, filename: str) -> List[Dependency]:
94+
"""
95+
Read the file and recursively search for dependencies
96+
:param filename: Root file from which search the dependencies
97+
"""
98+
return []
99+
100+
def __eq__(self, other):
101+
return self.name == other.name
102+
103+
def __hash__(self):
104+
return hash(self.name)
105+
106+
107+
class CompiledLanguage(Language, ABC):
108+
@property
109+
def need_compilation(self):
110+
return True
111+
112+
113+
class LanguageManager:
114+
LANGUAGES = [] # type: List[Language]
115+
EXT_CACHE = {} # type: Dict[str, Language]
116+
117+
@staticmethod
118+
def load_languages():
119+
languages = glob.glob(os.path.dirname(__file__) + "/*.py")
120+
for language in languages:
121+
if language.endswith("__init__.py"):
122+
continue
123+
lang_name = language.split("/")[-1][:-3]
124+
spec = importlib.util.spec_from_file_location(
125+
"task_maker.languages." + lang_name, language)
126+
module = importlib.util.module_from_spec(spec)
127+
spec.loader.exec_module(module)
128+
if not hasattr(module, "register"):
129+
raise ValueError(
130+
"Plugin for language {} does not have register hook".
131+
format(lang_name))
132+
module.register()
133+
134+
@staticmethod
135+
def register_language(language: Language):
136+
LanguageManager.LANGUAGES.append(language)
137+
for ext in language.source_extensions:
138+
if ext in LanguageManager.EXT_CACHE:
139+
raise ValueError(
140+
"Extension {} already registered for language {}".format(
141+
ext, LanguageManager.EXT_CACHE[ext].name))
142+
LanguageManager.EXT_CACHE[ext] = language
143+
144+
@staticmethod
145+
def from_file(path: str) -> Language:
146+
name, ext = os.path.splitext(path)
147+
if ext not in LanguageManager.EXT_CACHE:
148+
raise ValueError("Unknown language for file {}".format(path))
149+
return LanguageManager.EXT_CACHE[ext]
150+
151+
@staticmethod
152+
def valid_extensions() -> List[str]:
153+
return list(LanguageManager.EXT_CACHE.keys())
154+
155+
156+
def make_unique(deps: List[Dependency]) -> List[Dependency]:
157+
return list(item[1] for item in {dep.name: dep for dep in deps}.items())

python/languages/c.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
import os.path
3+
import re
4+
from typing import List
5+
6+
from task_maker.args import Arch
7+
from task_maker.languages import CompiledLanguage, CommandType, \
8+
LanguageManager, Dependency, make_unique
9+
10+
CXX_INCLUDE = re.compile('#include *["<](.+)[">]')
11+
12+
13+
def find_c_dependency(filename: str) -> List[Dependency]:
14+
scope = os.path.dirname(filename)
15+
with open(filename) as file:
16+
content = file.read()
17+
includes = CXX_INCLUDE.findall(content)
18+
dependencies = [] # type: List[Dependency]
19+
for include in includes:
20+
file_path = os.path.join(scope, include)
21+
if os.path.islink(file_path):
22+
file_path = os.path.realpath(file_path)
23+
# the sandbox does not support file inside subdirs (nor ../something),
24+
# for convenience skip all the files that includes "/" in the name
25+
if os.path.exists(file_path) and os.sep not in include:
26+
dependency = Dependency(include, file_path)
27+
dependencies += [dependency]
28+
dependencies += find_c_dependency(file_path)
29+
return dependencies
30+
31+
32+
class LanguageC(CompiledLanguage):
33+
@property
34+
def name(self):
35+
return "C"
36+
37+
@property
38+
def source_extensions(self):
39+
return [".c"]
40+
41+
@property
42+
def header_extensions(self):
43+
return [".h"]
44+
45+
def get_compilation_command(self, source_filenames: List[str],
46+
exe_name: str, for_evaluation: bool,
47+
target_arch: Arch) -> (CommandType, List[str]):
48+
cmd = ["cc"]
49+
if for_evaluation:
50+
cmd += ["-DEVAL"]
51+
if target_arch == Arch.I686:
52+
cmd += ["-m32"]
53+
cmd += ["-O2", "-std=c11", "-Wall", "-o", exe_name]
54+
cmd += source_filenames
55+
return CommandType.SYSTEM, cmd
56+
57+
def get_dependencies(self, filename: str):
58+
return make_unique(find_c_dependency(filename))
59+
60+
61+
def register():
62+
LanguageManager.register_language(LanguageC())

0 commit comments

Comments
 (0)