Rewrite the lunabuild toolchain with enhanced feature (#60)
[lunaix-os.git] / lunaix-os / scripts / build-tools / lcfg2 / common.py
1 import ast
2
3 from lib.utils  import SourceLogger, Schema
4
5 class NodeProperty:
6     class PropertyAccessor:
7         def __init__(self, key):
8             self.__key = key
9         def __getitem__(self, node):
10             return node.get_property(self.__key)
11         def __setitem__(self, node, value):
12             node.set_property(self.__key, value)
13         def __delitem__(self, node):
14             node.set_property(self.__key, None)
15
16     Token       = PropertyAccessor("$token")
17     Value       = PropertyAccessor("$value")
18     Type        = PropertyAccessor("$type")
19     Enabled     = PropertyAccessor("$enabled")
20     Status      = PropertyAccessor("$status")
21     Dependency  = PropertyAccessor("$depends")
22     Linkage     = PropertyAccessor("$linkage")
23     Hidden      = PropertyAccessor("hidden")
24     Parent      = PropertyAccessor("parent")
25     Label       = PropertyAccessor("label")
26     Readonly    = PropertyAccessor("readonly")
27     HelpText    = PropertyAccessor("help")
28
29 class ConfigNodeError(Exception):
30     def __init__(self, node, *args):
31         super().__init__(*args)
32
33         self.__node = node
34         self.__msg = " ".join([str(x) for x in args])
35
36     def __str__(self):
37         node = self.__node
38         tok: ast.stmt = NodeProperty.Token[node]
39         return (
40             f"{node._filename}:{tok.lineno}:{tok.col_offset}:" +
41             f" fatal error: {node._name}: {self.__msg}"
42         )
43
44
45 class ValueTypeConstrain:
46     TypeMap = {
47         "str": str,
48         "int": int,
49         "bool": bool,
50     }
51
52     BinOpOr = Schema(ast.BinOp, op=ast.BitOr)
53
54     def __init__(self, node, rettype):
55         self.__node = node
56         self.__raw = rettype
57         
58         if isinstance(rettype, ast.Expr):
59             value = rettype.value
60         else:
61             value = rettype
62         
63         if isinstance(value, ast.Constant):
64             self.schema = Schema(value.value)
65         
66         elif isinstance(value, ast.Name):
67             self.schema = Schema(self.__parse_type(value.id))
68         
69         elif isinstance(value, ast.BinOp):
70             unions = self.__parse_binop(value)
71             self.schema = Schema(Schema.Union(*unions))
72         
73         else:
74             raise Exception(
75                 f"unsupported type definition: {ast.unparse(rettype)}")
76
77     def __parse_type(self, type):
78         if type not in ValueTypeConstrain.TypeMap:
79             SourceLogger.warn(self.__node, self.__raw, 
80                               f"unknwon type: '{type}'. Fallback to 'str'")
81             return str
82         
83         return ValueTypeConstrain.TypeMap[type]
84     
85     def __parse_binop(self, oproot):
86         if isinstance(oproot, ast.Constant):
87             return [oproot.value]
88         
89         if ValueTypeConstrain.BinOpOr != oproot:
90             SourceLogger.warn(
91                 self.__node, self.__raw, 
92                 "only OR is allowed. Ignoring...")
93             return []
94         
95         return self.__parse_binop(oproot.left) \
96              + self.__parse_binop(oproot.right)
97     
98     def check_type(self, value):
99         return self.schema.match(value)
100     
101     def ensure_type(self, node, val):
102         if self.check_type(val):
103            return
104
105         raise node.config_error(
106                 f"unmatched type:",
107                 f"expect: '{self.schema}',",
108                 f"got: '{type(val).__name__}' (val: {val})") 
109
110 class NodeDependency:
111     class SimpleWalker(ast.NodeVisitor):
112         def __init__(self, deps):
113             super().__init__()
114             self.__deps = deps
115         
116         def visit_Name(self, node):
117             self.__deps._names.append(node.id)
118     
119     def __init__(self, nodes, expr: ast.expr):
120         self._names = []
121         self._expr  = ast.unparse(expr)
122
123         expr = ast.fix_missing_locations(expr)
124         self.__exec  = compile(ast.Expression(expr), "", mode='eval')
125
126         NodeDependency.SimpleWalker(self).visit(expr)
127
128     def evaluate(self, value_tables) -> bool:        
129         return eval(self.__exec, value_tables)
130     
131     @staticmethod
132     def try_create(node):
133         expr = NodeProperty.Dependency[node]
134         if not isinstance(expr, ast.expr):
135             return
136         
137         dep = NodeDependency(node, expr)
138         NodeProperty.Dependency[node] = dep
139
140
141 class NodeInverseDependency:
142     def __init__(self):
143         self.__map = {}
144
145     def add_linkage(self, name, expr):
146         if name not in self.__map:
147             self.__map[name] = [expr]
148         else:
149             self.__map[name].append(expr)
150
151     def linkages(self):
152         return self.__map.items()