import itertools
from enum import auto, Enum
from typing import Union, Optional, List, Sequence, Set, Tuple, Generic, TypeVar
import numpy as np
from webdnn.util import json
from webdnn.util.misc import mul
class PlaceholderOperator(Enum):
Add = auto() # v1 + v2
Mul = auto() # v1 * v2
Mod = auto() # v1 % v2
EQ = auto() # v1
FloorDiv = auto() # v1 // v2
T = TypeVar("T")
U = TypeVar("U")
class MutableCombinator(Generic[T]):
"""
Iterator for combination. This iterator is mutable (items can be removed/appended anytime)
Examples::
a = [1, 2, 3, 4]
comb = MutableCombinator(a, 2)
for i, j in comb:
print(i, j)
if i == 2:
comb.remove(i)
>>> (1, 2),
(1, 3),
(1, 4),
(2, 3), # Here, item "2" is removed and so combination "(2, 4)" does not appeared.
(3, 4)
print(comb.sequence)
>>> (1, 3, 4)
Arguments::
sequence (Sequence) : item sequence
r (int, optional) : length of each combination. Default is 2.
"""
def __init__(self, sequence: Sequence[T], r: int = 2):
self._sequence = list(sequence) # type: List[T]
self._combination = list(itertools.combinations(range(len(sequence)), r)) # type: List[Tuple[int, ...]]
self._removed_indices = set() # type: Set[int]
self._r = r # type: int
def __iter__(self):
return self
def __next__(self, *args, **kwargs):
while True:
if len(self._combination) == 0:
raise StopIteration
ret = self._combination.pop(0)
if all(i not in self._removed_indices for i in ret):
break
return tuple(self._sequence[i] for i in ret)
def remove(self, x):
i = -1
while True:
i = self._sequence.index(x, i + 1)
if i not in self._removed_indices:
break
self._removed_indices.add(i)
def append(self, x):
i = len(self._sequence)
self._sequence.append(x)
self._combination += [(i,) + others for others in itertools.combinations(range(i), self._r - 1)]
@property
def sequence(self) -> Tuple[T, ...]:
return tuple(v for i, v in enumerate(self._sequence) if i not in self._removed_indices)
class MutableProduct(Generic[T, U]):
"""
Iterator for product of 2 sequences. This iterator is mutable (items can be removed/appended anytime)
Examples::
a = [1, 2, 3, 4]
b = [5, 6, 7]
prod = MutableProduct(a, b)
for i, j in prod:
print(i, j)
prod.remove(0, i)
>>> (1, 5), # Here, item "1" is removed and so pair "(1, 6)" and "(1, 7) do not appeared.
(2, 5),
(3, 5),
(4, 5)
print(prod.sequences)
>>> ((), (5, 6, 7))
Arguments::
sequence1 (Sequence) : first sequence
sequence2 (Sequence) : second sequence
"""
def __init__(self, sequence1: Sequence[T], sequence2: Sequence[U]):
self._sequences = (list(sequence1), list(sequence2)) # type: Tuple[List[T], List[U]]
self._prod = list(itertools.product(range(len(sequence1)), range(len(sequence2)))) # type: List[Tuple[int, int]]
self._removed_indices = (set(), set()) # type: Tuple[Set[int],Set[int]]
def __iter__(self):
return self
def __next__(self) -> Tuple[T, U]:
while True:
if len(self._prod) == 0:
raise StopIteration
ret = self._prod.pop(0)
if ret[0] not in self._removed_indices[0] and ret[1] not in self._removed_indices[1]:
break
return self._sequences[0][ret[0]], self._sequences[1][ret[1]]
def remove(self, seq_index, x):
i = -1
while True:
i = self._sequences[seq_index].index(x, i + 1)
if i not in self._removed_indices[seq_index]:
break
self._removed_indices[seq_index].add(i)
def append(self, seq_index, x):
i = len(self._sequences[seq_index])
self._sequences[seq_index].append(x)
if seq_index == 0:
self._prod += [(i, j) for j in range(len(self._sequences[1]))]
else:
self._prod += [(j, i) for j in range(len(self._sequences[0]))]
@property
def sequences(self) -> Tuple[Tuple[T, ...], Tuple[U, ...]]:
return tuple(v for i, v in enumerate(self._sequences[0]) if i not in self._removed_indices[0]), \
tuple(v for i, v in enumerate(self._sequences[1]) if i not in self._removed_indices[1])
class Dependency:
operator: PlaceholderOperator
operands: List[Union[int, "Placeholder"]]
@staticmethod
def check_deep_equal(d1: "Dependency", d2: "Dependency") -> bool:
if d1.operator != d2.operator:
return False
if d1.operator == PlaceholderOperator.Mul or d1.operator == PlaceholderOperator.Add:
operands1 = sorted(d1.operands, key=lambda op: str(op))
operands2 = sorted(d2.operands, key=lambda op: str(op))
if len(operands1) != len(operands2):
return False
return all([Placeholder.check_deep_equal(p1, p2) for p1, p2 in zip(operands1, operands2)])
elif d1.operator == PlaceholderOperator.Mod or d1.operator == PlaceholderOperator.FloorDiv:
is_d1_same = Placeholder.check_deep_equal(d1.operands[0], d2.operands[0])
is_d2_same = Placeholder.check_deep_equal(d1.operands[1], d2.operands[1])
return is_d1_same and is_d2_same
def __init__(self, operator: PlaceholderOperator, operands: Sequence[Union[int, "Placeholder"]]):
if operator == PlaceholderOperator.Mul or operator == PlaceholderOperator.Add:
operands = list(sorted(operands, key=lambda op: str(op)))
self.operator = operator
self.operands = operands
@property
def is_resolved(self) -> bool:
"""
If true, all dependent placeholders are resolved
"""
if any((isinstance(operand, Placeholder) and not Placeholder.check_resolved(operand)) for operand in self.operands):
return False
return True
@property
def value(self) -> Union[int, "Placeholder"]:
if not self.is_resolved:
raise ValueError("Dependency is not resolved")
if self.operator == PlaceholderOperator.Add:
r = 0
for v in self.operands:
r += Placeholder.force_int(v)
return r
elif self.operator == PlaceholderOperator.Mul:
r = 1
for v in self.operands:
r *= Placeholder.force_int(v)
return r
elif self.operator == PlaceholderOperator.Mod:
return Placeholder.force_int(self.operands[0]) % Placeholder.force_int(self.operands[1])
elif self.operator == PlaceholderOperator.FloorDiv:
return Placeholder.force_int(self.operands[0]) // Placeholder.force_int(self.operands[1])
else:
raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}")
def __repr__(self):
if self.operator == PlaceholderOperator.Add:
return " + ".join(map(lambda x: x.__repr__(), self.operands))
s = []
for v in self.operands:
if Placeholder.check_resolved(v):
s.append(str(v))
elif v.dependency and len(v.dependency.operands) > 1:
s.append("(" + v.__repr__() + ")")
else:
s.append(v.__repr__())
if self.operator == PlaceholderOperator.Mul:
return " * ".join(s)
elif self.operator == PlaceholderOperator.Mod:
return " % ".join(s)
elif self.operator == PlaceholderOperator.FloorDiv:
return " // ".join(s)
raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}")
def dump(self):
if self.operator == PlaceholderOperator.Add:
return "(" + self.operands[0].dump() + " + " + self.operands[1].dump() + ")"
elif self.operator == PlaceholderOperator.Mul:
return self.operands[0].dump() + " * " + self.operands[1].dump()
elif self.operator == PlaceholderOperator.Mod:
return self.operands[0].dump() + " % " + self.operands[1].dump()
elif self.operator == PlaceholderOperator.FloorDiv:
return self.operands[0].dump() + " // " + self.operands[1].dump()
else:
raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}")
def generate_js_function(self):
if self.operator == PlaceholderOperator.Add:
return " + ".join(map(lambda x: x.generate_js_function(flag_semicolon=False), self.operands))
s = []
for v in self.operands:
if Placeholder.check_resolved(v):
s.append(str(v))
else:
if v.dependency and len(v.dependency.operands) > 1:
s.append("(" + v.generate_js_function(flag_semicolon=False) + ")")
else:
s.append(v.generate_js_function(flag_semicolon=False))
if self.operator == PlaceholderOperator.Mul:
return " * ".join(s)
elif self.operator == PlaceholderOperator.Mod:
return " % ".join(s)
elif self.operator == PlaceholderOperator.FloorDiv:
assert len(s) == 2
return f"Math.floor({s[0]} / {s[1]})"
raise NotImplementedError(f"Unsupported placeholder operation: {self.operator}")
_id = 0
[docs]class Placeholder(json.SerializableMixin):
"""Placeholder(value=None, label=None)
Placeholder represents values which are unknown at compile time and determined at runtime, like batch size, length of
time series, etc.
Placeholder supports only integer value.
Placeholder is embedded in graph descriptor and resolved at runtime like follows:
.. code-block:: javascript
runner = await WebDNN.load('./my_model');
await runner.setPlaceholderValue({
'N': 8
});
Also Placeholder can be resolved by setting concrete value at compile time.
>>> x = Placeholder(label="x")
>>> print(x)
<x>
>>> x.value = 3
>>> print(x)
3
>>> print(type(x))
<class 'webdnn.graph.placeholder.Placeholder'>
Placeholder supports follow basic operations.
- add(:code:`+`), sub(:code:`-`), mul(:code:`*`), mod(:code:`%`), floor-div(:code:`//`).
>>> x = Placeholder(label="x")
>>> y = x * 2 + 3
>>> print(y)
<x> * 2 + 3
>>> x = Placeholder(label="x")
>>> y = x % 4 // 5
>>> print(y)
(<x> % 4) // 5
If possible, equation are simplified
>>> x = Placeholder(label="x")
>>> y = x * 6 + x * 7
>>> print(y)
<x> * 13
- eq(:code:`==`), ne(:code:`!=`)
If both placeholders are resolved, they are compared based on concrete values.
>>> p = Placeholder(value=3)
>>> x = p * 2
>>> y = p + p
>>> print(x == y == 6)
True
If either placeholder is not resolved, they are compared symbolically.
>>> p = Placeholder(label="p")
>>> x = p * 2
>>> y = p * 3
>>> print(x == y)
False
>>> p.value = 0
>>> print(x == y)
True
- gt(:code:`>`), lt(:code:`<`), ge(:code:`>=`), le(:code:`<=`)
Supported only when both placeholders are resolved. Otherwise, an error is raised.
>>> p = Placeholder(label="p")
>>> x = p * 2
>>> y = p * 3
>>> print(x < y)
ValueError: First operand is unresolved placeholder. It can't be compared.
>>> p.value = 3
>>> print(x < y)
True
Attributes:
label (str): the label.
"""
_value = None # type: Optional[int]
label = None # type: Optional[str]
dependency = None # type: Optional[Dependency]
@staticmethod
[docs] def to_int(x: Union[int, "Placeholder"]):
"""to_int(x)
Convert the placeholder into concrete integer value.
Args:
x: the placeholder
Returns:
(int or Placeholder): If `x` is resolved, an integer is returned. Otherwise, `x` itself is returned.
"""
return int(x) if not isinstance(x, Placeholder) else int(x.value) if Placeholder.check_resolved(x) else x
@staticmethod
[docs] def force_int(x: Union[int, "Placeholder"]):
"""force_int(x)
Convert the placeholder into concrete integer value. If `x` is not resolved, an error is raised.
Args:
x: the placeholder
Returns:
(int): an integer
"""
if not isinstance(x, Placeholder):
return int(x)
elif Placeholder.check_resolved(x):
return x.value
raise ValueError(f"{x} is not resolved.")
@staticmethod
[docs] def check_resolved(x: Union[int, "Placeholder"]):
"""check_resolved(x)
Check whether specified placeholder is resolved or not.
Args:
x: the placeholder
Returns:
(bool): If `True`, the placeholder is resolved. Otherwise, it's not resolved.
"""
if not isinstance(x, Placeholder):
return True
if x._value is not None:
return True
if x.dependency:
return x.dependency.is_resolved
return False
@staticmethod
def check_deep_equal(p1: Union[int, "Placeholder"], p2: Union[int, "Placeholder"]) -> bool:
if str(p1) == str(p2):
return True
elif Placeholder.check_resolved(p1) and Placeholder.check_resolved(p2):
return Placeholder.force_int(p1) == Placeholder.force_int(p2)
elif Placeholder.check_resolved(p1) or Placeholder.check_resolved(p2):
return False
elif p1.dependency is not None and p2.dependency is not None:
return Dependency.check_deep_equal(p1.dependency, p2.dependency)
else:
return False
def __new__(cls, dependency: Optional[Dependency] = None, value: Union[int, "Placeholder"] = None, label: str = None):
if isinstance(value, Placeholder):
return value
return super().__new__(cls)
def __init__(self, dependency: Optional[Dependency] = None, value: Union[int, "Placeholder"] = None, label: Optional[str] = None):
global _id
if self is value:
return
self.dependency = dependency
if label is None:
self.label = f"placeholder_[{_id}]"
_id += 1
else:
self.label = label
if value is not None:
self.value = value
@property
def value(self) -> Union[int, "Placeholder"]:
"""value
The placeholder's value. If it's not resolved, the placeholder itself is returned.
If the placeholder is already resolved, new value cannot be set, and it causes an error.
"""
if Placeholder.check_resolved(self):
if self.dependency:
if self._value is None:
self._value = self.dependency.value
return self._value
else:
return self
@value.setter
def value(self, new_v: int):
if Placeholder.check_resolved(self):
raise ValueError(f"{self} is already resolved")
elif isinstance(new_v, int) or isinstance(new_v, np.int32) or isinstance(new_v, np.int64):
# noinspection PyTypeChecker
self._value = int(new_v)
else:
raise TypeError(f"Placeholder#value must be a int, not '{type(new_v)}'")
def unify(self, other: Union[int, "Placeholder"]):
if Placeholder.check_resolved(self) and Placeholder.check_resolved(other):
assert self == other, f"""
Unification failed: self != other
(self) = {self}
(other) = {other}"""
elif Placeholder.check_resolved(self) and not Placeholder.check_resolved(other):
other.value = self.value
elif not Placeholder.check_resolved(self) and Placeholder.check_resolved(self):
self.value = other.value
else:
# FIXME
pass
@property
def _operator(self):
if Placeholder.check_resolved(self):
return None
elif self.dependency is None:
return None
else:
return self.dependency.operator
@property
def _operands(self):
if Placeholder.check_resolved(self):
return [self.value]
elif self.dependency is None:
return [self]
else:
return list(self.dependency.operands)
def __neg__(self) -> Union[int, "Placeholder"]:
return -1 * self
def __add__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
v1, v2 = self, Placeholder(value=other)
if v2 == 0:
return v1
if v1 == 0:
return v2
if Placeholder.check_resolved(v1) and Placeholder.check_resolved(v2):
return v1.value + v2.value
# flatten: (v1_0 + v1_1) + (v2_0 + v2_1) -> (v1_0 + v1_1 + v1_2 + v1_3)
terms = [] # type: List[Union[Placeholder, int]]
terms += list(v1._operands) if v1._operator == PlaceholderOperator.Add else [v1]
terms += list(v2._operands) if v2._operator == PlaceholderOperator.Add else [v2]
len_terms = len(terms)
# sum resolved values
resolved_value = 0
for vv in list(terms):
if Placeholder.check_resolved(vv):
terms.remove(vv)
resolved_value += vv
if resolved_value != 0:
terms.append(resolved_value)
# factorize: (v1 * v3) + (v2 * v3) -> (v1+v2) * v3
comb = MutableCombinator(terms)
for vv1, vv2 in comb:
if not isinstance(vv1, Placeholder) or not isinstance(vv2, Placeholder):
continue
if (vv1._operator is None or vv1._operator == PlaceholderOperator.Mul) and \
(vv2._operator is None or vv2._operator == PlaceholderOperator.Mul):
operands_vv1 = list(vv1._operands)
operands_vv2 = list(vv2._operands)
common_term = list()
for vvv in list(operands_vv1):
if vvv in operands_vv2:
operands_vv1.remove(vvv)
operands_vv2.remove(vvv)
common_term.append(vvv)
if len(common_term) >= 1:
# If either vv1 or vv2 is not resolved, this rule conflicts with expanding in __mul__
if Placeholder.check_resolved(mul(operands_vv1)) and Placeholder.check_resolved(mul(operands_vv2)):
comb.remove(vv1)
comb.remove(vv2)
comb.append(mul(common_term) * (mul(operands_vv1) + mul(operands_vv2)))
terms = comb.sequence
if len(terms) < len_terms:
return sum(terms)
return Placeholder(Dependency(PlaceholderOperator.Add, terms))
def __radd__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
return Placeholder(value=other) + self
def __sub__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
if other == 0:
return self
return self + (-1 * other)
def __rsub__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
return Placeholder(value=other) - self
def __mul__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
v1, v2 = self, Placeholder(value=other)
if v1 == 0 or v2 == 0:
return 0
if v1 == 1:
return v2
if v2 == 1:
return v1
if Placeholder.check_resolved(v1) and Placeholder.check_resolved(v2):
return v1.value * v2.value
# flatten: (v1_0 * v1_1) * (v2_0 * v2_1) -> (v1_0 * v1_1 * v1_2 * v1_3)
terms = []
terms += list(v1._operands) if v1._operator == PlaceholderOperator.Mul else [v1]
terms += list(v2._operands) if v2._operator == PlaceholderOperator.Mul else [v2]
# sum resolved values
resolved_value = 1
for vv in list(terms):
if Placeholder.check_resolved(vv):
terms.remove(vv)
resolved_value *= vv
if resolved_value != 1:
terms.append(resolved_value)
for vv in terms:
if isinstance(vv, Placeholder) and vv._operator == PlaceholderOperator.Add:
# expanding: (v1_0 + v1_1 + ...) * v2 = (v1_0 * v2) + (v1_1 * v2) + ...
terms.remove(vv)
return sum(mul(terms) * v for v in vv._operands)
return Placeholder(Dependency(PlaceholderOperator.Mul, terms))
def __rmul__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
return Placeholder(value=other) * self
def __floordiv__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
v1, v2 = self, Placeholder(value=other)
if v2 == 1:
return v1
if v1 == 0:
return 0
if v1 == v2:
return 1
if Placeholder.check_resolved(v1) and Placeholder.check_resolved(v2):
return v1.value // v2.value
if v1._operator == PlaceholderOperator.Add:
operands_v1 = list(v1._operands)
divided = []
for vv1 in list(operands_v1):
if vv1 % v2 != 0:
continue
operands_v1.remove(vv1)
divided.append(vv1 // v2)
if len(divided) > 0:
return sum(divided + [sum(operands_v1) // v2])
if v1._operator == PlaceholderOperator.FloorDiv:
v1_1, v1_2 = v1._operands
if Placeholder.check_resolved(v1_2) and Placeholder.check_resolved(v2):
return v1_1 // (v1_2 * v2)
if (v1._operator is None or v1._operator == PlaceholderOperator.Mul) and \
(v2._operator is None or v2._operator == PlaceholderOperator.Mul):
operands_v1 = list(v1._operands)
operands_v2 = list(v2._operands)
prod = MutableProduct(operands_v1, operands_v2)
for vv1, vv2 in prod:
if vv1 % vv2 != 0:
continue
prod.remove(0, vv1)
prod.remove(1, vv2)
prod.append(0, vv1 // vv2)
new_operands_v1, new_operands_v2 = prod.sequences
if len(new_operands_v2) == 0:
return mul(new_operands_v1)
else:
if len(new_operands_v1) == 0:
# 1 // v2 == 0
return 0
else:
v1 = mul(new_operands_v1)
v2 = mul(new_operands_v2)
return Placeholder(Dependency(PlaceholderOperator.FloorDiv, [v1, v2]))
return Placeholder(Dependency(PlaceholderOperator.FloorDiv, [v1, v2]))
def __rfloordiv__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
return Placeholder(value=other) // self
def __mod__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
v1, v2 = self, Placeholder(value=other)
if v1 == v2:
return 0
if v2 == 1:
return 0
if Placeholder.check_resolved(v1) and Placeholder.check_resolved(v2):
return v1.value % v2.value
if (v1._operator is None or v1._operator == PlaceholderOperator.Mul) and (
v2._operator is None or v2._operator == PlaceholderOperator.Mul):
# v = ... + (common_term * other_term1) % (common_term * other_term2) + ...
# = ... + (other_term1 % other_term2) * common_term + ...
prod = MutableProduct(v1._operands, v2._operands)
common_term = []
flag_updated = False
for vv1, vv2 in prod:
if (Placeholder.check_resolved(vv1) and Placeholder.check_resolved(vv2) and vv1 % vv2 == 0) or vv1 == vv2:
flag_updated = True
prod.remove(0, vv1)
prod.remove(1, vv2)
prod.append(0, vv1 // vv2)
common_term.append(vv2)
if flag_updated:
new_operands_v1, new_operands_v2 = prod.sequences
return (mul(new_operands_v1) % mul(new_operands_v2)) * mul(common_term)
return Placeholder(Dependency(PlaceholderOperator.Mod, [v1, v2]))
def __rmod__(self, other: Union[int, "Placeholder"]) -> Union[int, "Placeholder"]:
return Placeholder(value=other) % self
def __int__(self):
return Placeholder.force_int(self)
def __eq__(self, other: Union[int, "Placeholder"]) -> bool:
return Placeholder.check_deep_equal(self, other)
def __ne__(self, other: Union[int, "Placeholder"]) -> bool:
return not Placeholder.check_deep_equal(self, other)
def __gt__(self, other: Union[int, "Placeholder"]) -> bool:
if not Placeholder.check_resolved(self):
raise ValueError("First operand is unresolved placeholder. It can't be compared.")
if not Placeholder.check_resolved(other):
raise ValueError("Second operand is unresolved placeholder. It can't be compared.")
return self.value > Placeholder.force_int(other)
def __lt__(self, other: Union[int, "Placeholder"]) -> bool:
if not Placeholder.check_resolved(self):
raise ValueError("First operand is unresolved placeholder. It can't be compared.")
if not Placeholder.check_resolved(other):
raise ValueError("Second operand is unresolved placeholder. It can't be compared.")
return self.value < Placeholder.force_int(other.value)
def __ge__(self, other: Union[int, "Placeholder"]) -> bool:
if not Placeholder.check_resolved(self):
raise ValueError("First operand is unresolved placeholder. It can't be compared.")
if not Placeholder.check_resolved(other):
raise ValueError("Second operand is unresolved placeholder. It can't be compared.")
return self.value >= Placeholder.force_int(other)
def __le__(self, other: Union[int, "Placeholder"]) -> bool:
if not Placeholder.check_resolved(self):
raise ValueError("First operand is unresolved placeholder. It can't be compared.")
if not Placeholder.check_resolved(other):
raise ValueError("Second operand is unresolved placeholder. It can't be compared.")
return self.value <= Placeholder.force_int(other)
def __repr__(self):
if Placeholder.check_resolved(self):
return str(self.value)
else:
if self.dependency:
return self.dependency.__repr__()
else:
return f"<{self.label}>" if self.label else f"<{self.__class__.__name__} at {hex(id(self))}>"
def __hash__(self):
return id(self)
def dump(self):
if self.dependency:
return self.dependency.dump()
elif self._value is not None:
return str(self._value)
else:
return f"<{self.label}>" if self.label else f"<{self.__class__.__name__} at {hex(id(self))}>"
def _to_serializable_(self):
if Placeholder.check_resolved(self):
return self.value
else:
return {
"eval": self.generate_js_function()
}
[docs] def get_depend_placeholders(self):
"""get_depend_placeholders
List up all dependent placeholders
Returns:
(list of Placeholder): list of all dependent placeholders
"""
if Placeholder.check_resolved(self):
return set()
if self.dependency:
res = set()
for v in self.dependency.operands:
if not Placeholder.check_resolved(v):
res.update(v.get_depend_placeholders())
return res
else:
return {self}
[docs] def generate_js_function(self, flag_semicolon=True):
"""generate_js_function
Generate javascript code to resolve this placeholder's value at runtime.
Args:
flag_semicolon(bool): If True, semicolon is appended into generated code.
Returns:
(str): generated code
"""
if Placeholder.check_resolved(self):
return f"{self.value}" + (";" if flag_semicolon else "")
else:
if self.dependency:
return self.dependency.generate_js_function()
else:
return f"placeholders['{self.label}']" + (";" if flag_semicolon else "")