def validate(self, errors, pos, val, errstr = ''): if util.keysearch(val, 0, self.enums) == None: err_add(errors, pos, 'TYPE_VALUE', (val, self.definition, 'enum not defined' + errstr)) return False else: return True
def validate(self, errors, pos, val, errstr = ''): for v in val: if util.keysearch(v, 0, self.bits) == None: err_add(errors, pos, 'TYPE_VALUE', (v, self.definition, 'bit not defined' + errstr)) return False return True
def get_keyword(self): """ret: identifier | (prefix, identifier)""" self.skip() m = syntax.re_keyword.match(self.buf) if m == None: error.err_add(self.errors, self.pos, 'SYNTAX_ERROR', 'illegal keyword: ' + self.buf) raise error.Abort else: self.set_buf(m.end()) # check the separator if (self.buf[0].isspace() or (self.buf[0] == '/' and self.buf[1] in ('/', '*')) or (self.buf[0] in (';','{'))): pass else: error.err_add(self.errors, self.pos, 'SYNTAX_ERROR', 'expected separator, got: "' + self.buf[:6] + '..."') raise error.Abort if m.group(2) == None: # no prefix return m.group(3) else: return (m.group(2), m.group(3))
def validate(self, errors, pos, val, errstr=''): if val < self.min or val > self.max: err_add(errors, pos, 'TYPE_VALUE', (str(val), self.definition, 'range error' + errstr)) return False else: return True
def validate(self, errors, pos, val, errstr = ''): if val < self.min or val > self.max: err_add(errors, pos, 'TYPE_VALUE', (str(val), self.definition, 'range error' + errstr)) return False else: return True
def validate(self, errors, pos, val, errstr=''): if util.keysearch(val, 0, self.enums) == None: err_add(errors, pos, 'TYPE_VALUE', (val, self.definition, 'enum not defined' + errstr)) return False else: return True
def validate(self, errors, pos, val, errstr=''): for v in val: if util.keysearch(v, 0, self.bits) == None: err_add(errors, pos, 'TYPE_VALUE', (v, self.definition, 'bit not defined' + errstr)) return False return True
def get_keyword(self): """ret: identifier | (prefix, identifier)""" self.skip() m = syntax.re_keyword.match(self.buf) if m == None: error.err_add(self.errors, self.pos, 'SYNTAX_ERROR', 'illegal keyword: ' + self.buf) raise error.Abort else: self.set_buf(m.end()) # check the separator if (self.buf[0].isspace() or (self.buf[0] == '/' and self.buf[1] in ('/', '*')) or (self.buf[0] in (';', '{'))): pass else: error.err_add( self.errors, self.pos, 'SYNTAX_ERROR', 'expected separator, got: "' + self.buf[:6] + '..."') raise error.Abort if m.group(2) == None: # no prefix return m.group(3) else: return (m.group(2), m.group(3))
def parse(self, ctx, ref, text): """Parse the string `text` containing a YIN (sub)module. Return a Statement on success or None on failure. """ self.ctx = ctx self.pos = error.Position(ref) self.top = None self.uri = None self.nsmap = {} self.prefixmap = {} self.included = [] self.extensions = {} self.data = '' self.element_stack = [] try: self.parser.Parse(text, True) except error.Abort: return None except expat.ExpatError, ex: self.pos.line = ex.lineno error.err_add(self.ctx.errors, self.pos, 'SYNTAX_ERROR', str(ex).split(":")[0]) return None
def validate_range_expr(errors, stmt, type_): # break the expression apart def f(lostr, histr): if histr == '': # this means that a single number was in the range, e.g. # "4 | 5..6". return (type_.i_type_spec.str_to_val(errors, stmt.pos, lostr), None) return (type_.i_type_spec.str_to_val(errors, stmt.pos, lostr), type_.i_type_spec.str_to_val(errors, stmt.pos, histr)) ranges = [f(m[1], m[6]) for m in syntax.re_range_part.findall(stmt.arg)] # make sure the range values are of correct type and increasing pos = stmt.pos cur_lo = None for (lo, hi) in ranges: if lo != 'min' and lo != 'max': type_.i_type_spec.validate(errors, pos, lo) if hi != 'min' and hi != 'max' and hi != None: type_.i_type_spec.validate(errors, pos, hi) # check that cur_lo < lo < hi if not is_smaller(cur_lo, lo): err_add(errors, pos, 'RANGE_BOUNDS', (str(lo), cur_lo)) return None if not is_smaller(lo, hi): err_add(errors, pos, 'RANGE_BOUNDS', (str(hi), str(lo))) return None if hi == None: cur_lo = lo else: cur_lo = hi return (ranges, stmt.pos)
def str_to_val(self, errors, pos, str): if str == 'true': return True elif str == 'false': return False else: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not a boolean')) return None
def _parse_statement(self, parent): keywd = self.tokenizer.get_keyword() # check for argument tok = self.tokenizer.peek() if tok == '{' or tok == ';': arg = None else: arg = self.tokenizer.get_string() stmt = statements.Statement(self.top, parent, self.pos, keywd, arg) if self.top is None: self.pos.top = stmt self.top = stmt # check for substatements tok = self.tokenizer.peek() if tok == '{': self.tokenizer.skip_tok() # skip the '{' while self.tokenizer.peek() != '}': substmt = self._parse_statement(stmt) stmt.substmts.append(substmt) self.tokenizer.skip_tok() # skip the '}' elif tok == ';': self.tokenizer.skip_tok() # skip the ';' else: error.err_add(self.ctx.errors, self.pos, 'INCOMPLETE_STATEMENT', (keywd, tok)) raise error.Abort return stmt
def start_element(self, name, attrs): name = str(name) # convert from unicode strings self.pos.line = self.lineno (ns, local_name) = self.split_qname(name) e = Element(ns, local_name, attrs, self.pos) if self.data.lstrip() != '': error.err_add(self.ctx.errors, self.pos, 'SYNTAX_ERROR', "unexpected element - mixed content") self.data = '' if self.element_stack == []: # this is the top-level element self.top = e self.element_stack.append(e) # special case - the top-level statement has its argument # as an attribute, so we can save it here try: (argname, _arg_is_elem) = syntax.yin_map[e.local_name] arg = e.find_attribute(argname) self.pos.top_name = arg except: pass return else: parent = self.element_stack[-1] parent.children.append(e) self.element_stack.append(e)
def _parse_statement(self, parent): keywd = self.tokenizer.get_keyword() # check for argument tok = self.tokenizer.peek() if tok == '{' or tok == ';': arg = None else: arg = self.tokenizer.get_string() stmt = statements.Statement(self.top, parent, self.pos, keywd, arg) if self.top is None: self.pos.top_name = arg self.top = stmt # check for substatements tok = self.tokenizer.peek() if tok == '{': self.tokenizer.skip_tok() # skip the '{' while self.tokenizer.peek() != '}': substmt = self._parse_statement(stmt) stmt.substmts.append(substmt) self.tokenizer.skip_tok() # skip the '}' elif tok == ';': self.tokenizer.skip_tok() # skip the ';' else: error.err_add(self.ctx.errors, self.pos, 'INCOMPLETE_STATEMENT', (keywd, tok)) raise error.Abort return stmt
def str_to_val(self, errors, pos, str): if str == 'true': return True; elif str == 'false': return False else: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not a boolean')) return None
def create_statement(self, e, parent): if e.ns == yin_namespace: keywd = e.local_name try: (argname, arg_is_elem) = syntax.yin_map[keywd] except KeyError: error.err_add(self.ctx.errors, e.pos, 'UNKNOWN_KEYWORD', keywd) return None else: # extension try: prefix = self.prefixmap[e.ns] except KeyError: error.err_add(self.ctx.errors, e.pos, 'MODULE_NOT_IMPORTED', e.ns) return None keywd = (prefix, e.local_name) keywdstr = util.keyword_to_str(keywd) res = self.find_extension(e.ns, e.local_name) if res is None: error.err_add(self.ctx.errors, e.pos, 'UNKNOWN_KEYWORD', keywdstr) return None (arg_is_elem, argname) = res keywdstr = util.keyword_to_str(keywd) if arg_is_elem == True: # find the argument element arg_elem = e.find_child(e.ns, argname) if arg_elem is None: arg = None error.err_add(self.ctx.errors, e.pos, 'MISSING_ARGUMENT_ELEMENT', (argname, keywdstr)) else: arg = arg_elem.data e.remove_child(arg_elem) elif arg_is_elem == False: arg = e.find_attribute(argname) if arg is None: error.err_add(self.ctx.errors, e.pos, 'MISSING_ARGUMENT_ATTRIBUTE', (argname, keywdstr)) else: e.remove_attribute(argname) else: # no arguments arg = None self.check_attr(e.pos, e.attrs) stmt = statements.Statement(self.top, parent, e.pos, keywd, arg) if self.top is None: self.top = stmt else: parent.substmts.append(stmt) for ch in e.children: self.create_statement(ch, stmt)
def str_to_val(self, errors, pos, str): dbg('trying to convert "%s" to a boolean...' % str) if str == 'true': return True elif str == 'false': return False else: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not a boolean')) return None
def str_to_val(self, errors, pos, str): dbg('trying to convert "%s" to a boolean...' % str) if str == 'true': return True; elif str == 'false': return False else: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not a boolean')) return None
def str_to_val(self, errors, pos, str): try: if str in ['min', 'max']: return str return int(str, 0) except ValueError: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not an integer')) return None
def str_to_val(self, errors, pos, str): try: dbg('trying to convert "%s" to an int...' % str) if str in ['min', 'max']: return str return int(str, 0) except ValueError: err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'not an integer')) return None
def validate(self, errors, pos, val, errstr=''): if self.base.validate(errors, pos, val, errstr) == False: return False for (re, re_pos) in self.res: if re.regexpExec(val) != 1: err_add(errors, pos, 'TYPE_VALUE', (val, self.definition, 'pattern mismatch' + errstr + ' for pattern defined at ' + str(re_pos))) return False return True
def readline(self): if len(self.lines) == 0: raise error.Eof self.buf = self.lines[0] del self.lines[0] self.pos.line += 1 self.offset = 0 if (self.max_line_len is not None and len(self.buf) > self.max_line_len): error.err_add(self.errors, self.pos, 'LONG_LINE', (len(self.buf), self.max_line_len))
def validate(self, errors, pos, val, errstr=''): if self.base.validate(errors, pos, val, errstr) == False: return False for (lo, hi) in self.ranges: if ((lo == 'min' or val >= lo) and ((hi == None and val == lo) or hi == 'max' or val <= hi)): return True err_add(errors, pos, 'TYPE_VALUE', (str(val), self.definition, 'range error' + errstr + ' for range defined at ' + str(self.ranges_pos))) return False
def check_attr(self, pos, attrs): """Check for unknown attributes.""" for at in attrs: (ns, local_name) = self.split_qname(at) if ns is None: error.err_add(self.ctx.errors, pos, 'UNEXPECTED_ATTRIBUTE', local_name) elif ns == yin_namespace: error.err_add(self.ctx.errors, pos, 'UNEXPECTED_ATTRIBUTE', "{"+at)
def validate(self, errors, pos, str, errstr = ''): # try to validate against each membertype for t in self.types: if t.i_type_spec != None: val = t.i_type_spec.str_to_val([], pos, str) if val != None: if t.i_type_spec.validate([], pos, val): return True; err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'no member type matched' + errstr)) return False
def validate(self, errors, pos, str, errstr=''): # try to validate against each membertype for t in self.types: if t.i_type_spec != None: val = t.i_type_spec.str_to_val([], pos, str) if val != None: if t.i_type_spec.validate([], pos, val): return True err_add(errors, pos, 'TYPE_VALUE', (str, self.definition, 'no member type matched' + errstr)) return False
def validate(self, errors, pos, val, errstr=''): if self.base.validate(errors, pos, val, errstr) == False: return False vallen = len(val) for (lo, hi) in self.lengths: if ((lo == 'min' or vallen >= lo) and ((hi == None and vallen == lo) or hi == 'max' or vallen <= hi)): return True err_add(errors, pos, 'TYPE_VALUE', (val, self.definition, 'length error' + errstr + ' for length defined at ' + str(self.length_pos))) return False
def search_module(self, pos, modulename, revision=None): """Searches for a module named `modulename` in the repository If the module is found, it is added to the context. Returns the module if found, and None otherwise""" if modulename not in self.revs: # this module doesn't exist in the repos at all error.err_add(self.errors, pos, 'MODULE_NOT_FOUND', modulename) # keep track of this to avoid multiple errors self.revs[modulename] = [] return None elif self.revs[modulename] == []: # this module doesn't exist in the repos at all, error reported return None if revision is not None: if (modulename, revision) in self.modules: return self.modules[(modulename, revision)] self._ensure_revs(self.revs[modulename]) x = util.keysearch(revision, 0, self.revs[modulename]) if x is not None: (_revision, handle) = x if handle == None: # this revision doesn't exist in the repos, error reported return None else: # this revision doesn't exist in the repos error.err_add(self.errors, pos, 'MODULE_NOT_FOUND_REV', (modulename, revision)) # keep track of this to avoid multiple errors self.revs[modulename].append((revision, None)) return None else: # get the latest revision (revision, handle) = self._get_latest_rev(self.revs[modulename]) if (modulename, revision) in self.modules: return self.modules[(modulename, revision)] if handle is None: module = None elif handle[0] == 'parsed': module = handle[1] ref = handle[2] if modulename != module.arg: error.err_add(self.errors, module.pos, 'BAD_MODULE_NAME', (module.arg, ref, modulename)) module = None else: module = self.add_parsed_module(handle[1]) else: # get it from the repos try: r = self.repository.get_module_from_handle(handle) (ref, format, text) = r module = self.add_module(ref, text, format, modulename, revision) except self.repository.ReadError, ex: error.err_add(self.errors, pos, 'READ_ERROR', str(ex)) module = None
def search_module(self, pos, modulename, revision=None): """Searches for a module named `modulename` in the repository If the module is found, it is added to the context. Returns the module if found, and None otherwise""" if modulename not in self.revs: # this module doesn't exist in the repos at all error.err_add(self.errors, pos, 'MODULE_NOT_FOUND', modulename) # keep track of this to avoid multiple errors self.revs[modulename] = [] return None elif self.revs[modulename] == []: # this module doesn't exist in the repos at all, error reported return None if revision is not None: if (modulename,revision) in self.modules: return self.modules[(modulename, revision)] self._ensure_revs(self.revs[modulename]) x = util.keysearch(revision, 0, self.revs[modulename]) if x is not None: (_revision, handle) = x if handle == None: # this revision doesn't exist in the repos, error reported return None else: # this revision doesn't exist in the repos error.err_add(self.errors, pos, 'MODULE_NOT_FOUND_REV', (modulename, revision)) # keep track of this to avoid multiple errors self.revs[modulename].append((revision, None)) return None else: # get the latest revision (revision, handle) = self._get_latest_rev(self.revs[modulename]) if (modulename, revision) in self.modules: return self.modules[(modulename, revision)] if handle is None: module = None elif handle[0] == 'parsed': module = handle[1] ref = handle[2] if modulename != module.arg: error.err_add(self.errors, module.pos, 'BAD_MODULE_NAME', (module.arg, ref, modulename)) module = None else: module = self.add_parsed_module(handle[1]) else: # get it from the repos try: r = self.repository.get_module_from_handle(handle) (ref, format, text) = r module = self.add_module(ref, text, format, modulename, revision) except self.repository.ReadError, ex: error.err_add(self.errors, pos, 'READ_ERROR', str(ex)) module = None
def validate(self): uris = {} for k in self.modules: m = self.modules[k] if m != None: namespace = m.search_one('namespace') if namespace != None: uri = namespace.arg if uri in uris: if uris[uri] != m.arg: error.err_add(self.errors, namespace.pos, 'DUPLICATE_NAMESPACE', (uri, uris[uri])) else: uris[uri] = m.arg
def validate_pattern_expr(errors, stmt): # check that it's syntactically correct try: import libxml2 try: re = libxml2.regexpCompile(stmt.arg) return (re, stmt.pos) except libxml2.treeError, v: err_add(errors, stmt.pos, 'PATTERN_ERROR', str(v)) return None except ImportError: err_add(errors, stmt.pos, 'PATTERN_FAILURE', "Could not import python module libxml2 " "(see http://xmlsoft.org for installation help)") return None
def validate_pattern_expr(errors, stmt): # check that it's syntactically correct try: import libxml2 try: re = libxml2.regexpCompile(stmt.arg) return (re, stmt.pos) except libxml2.treeError, v: err_add(errors, stmt.pos, 'PATTERN_ERROR', str(v)) return None except ImportError: err_add( errors, stmt.pos, 'PATTERN_FAILURE', "Could not import python module libxml2 " "(see http://xmlsoft.org for installation help)") return None
class YangParser(object): def __init__(self, extra={}): pass def parse(self, ctx, ref, text): """Parse the string `text` containing a YANG statement. Return a Statement on success or None on failure """ self.ctx = ctx self.pos = error.Position(ref) self.top = None try: self.tokenizer = YangTokenizer(text, self.pos, ctx.errors) stmt = self._parse_statement(None) except error.Abort: return None except error.Eof, e: error.err_add(self.ctx.errors, self.pos, 'EOF_ERROR', ()) return None try: # we expect a error.Eof at this point, everything else is an error self.tokenizer.peek() except error.Eof: return stmt except: pass error.err_add(self.ctx.errors, self.pos, 'TRAILING_GARBAGE', ()) return None
def validate_pattern_expr(errors, stmt): # check that it's syntactically correct try: import libxml2 try: re = libxml2.regexpCompile(stmt.arg) return (re, stmt.pos) except libxml2.treeError, v: err_add(errors, stmt.pos, 'PATTERN_ERROR', str(v)) return None except ImportError: ## Do not report a warning in this case. Maybe we should add some ## flag to turn on this warning... # err_add(errors, stmt.pos, 'PATTERN_FAILURE', # "Could not import python module libxml2 " # "(see http://xmlsoft.org for installation help)") return None
def parse(self, ctx, ref, text): """Parse the string `text` containing a YANG statement. Return a Statement on success or None on failure """ self.ctx = ctx self.pos = error.Position(ref) self.top = None try: self.tokenizer = YangTokenizer(text, self.pos, ctx.errors) stmt = self._parse_statement(None) except error.Abort: return None except error.Eof, e: error.err_add(self.ctx.errors, self.pos, 'EOF_ERROR', ()) return None
def validate_enums(errors, enums, stmt): if enums == []: err_add(errors, stmt.pos, 'MISSING_TYPE_SPEC', ('enumeration', 'enum')) return None # make sure all names and values given are unique names = {} values = {} next = 0 for e in enums: e.i_value = None value = e.search_one('value') if value is not None: try: x = int(value.arg) e.i_value = x if x < -2147483648 or x > 2147483647: raise ValueError if x >= next: next = x + 1 if x in values: err_add(errors, value.pos, 'DUPLICATE_ENUM_VALUE', (x, values[x])) else: values[x] = value.pos except ValueError: err_add(errors, value.pos, 'ENUM_VALUE', value.arg) else: # auto-assign a value values[next] = e.pos if next > 2147483647: err_add(errors, e.pos, 'ENUM_VALUE', str(next)) e.i_value = next next = next + 1 if e.arg in names: err_add(errors, e.pos, 'DUPLICATE_ENUM_NAME', (e.arg, names[e.arg])) else: names[e.arg] = e.pos # check status (here??) return enums
def str_to_val(self, errors, pos, s0): if s0 in ('min', 'max'): return s0 # make sure it is syntactically correct if syntax.re_decimal.search(s0) is None: err_add(errors, pos, 'TYPE_VALUE', (s0, self.definition, 'not a decimal')) return None if s0[0] == '-': is_negative = True s = s0[1:] else: is_negative = False s = s0 p = s.find('.') if p == -1: v = int(s) i = self.fraction_digits while i > 0: v = v * 10 i -= 1 else: v = int(s[:p]) i = self.fraction_digits j = p + 1 # slen = len(s.rstrip('0')) # ignore trailing zeroes # No, do not ignore trailing zeroes! slen = len(s) while i > 0: v *= 10 i -= 1 if j < slen: v += int(s[j]) j += 1 if j < slen: err_add(errors, pos, 'TYPE_VALUE', (s, self.definition, 'too many fraction digits')) return None if is_negative: v = -v return Decimal64Value(v, s0)
def add_parsed_module(self, module): if module is None: return None if module.arg is None: error.err_add(self.errors, module.pos, 'EXPECTED_ARGUMENT', module.keyword) return None top_keywords = ['module', 'submodule'] if module.keyword not in top_keywords: error.err_add(self.errors, module.pos, 'UNEXPECTED_KEYWORD_N', (module.keyword, top_keywords)) return None rev = util.get_latest_revision(module) if (module.arg, rev) in self.modules: other = self.modules[(module.arg, rev)] if (hasattr(other, 'i_adler32') and hasattr(module, 'i_adler32') and other.i_adler32 != module.i_adler32): error.err_add(self.errors, module.pos, 'DUPLICATE_MODULE', (module.arg, other.pos)) return None # exactly same module return other self.modules[(module.arg, rev)] = module statements.validate_module(self, module) return module
def str_to_val(self, errors, pos, s): if s.find(":") == -1: prefix = None name = s else: [prefix, name] = s.split(':', 1) if prefix is None or self.base.i_module.i_prefix == prefix: # check local identities pmodule = self.base.i_module else: # this is a prefixed name, check the imported modules pmodule = statements.prefix_to_module(self.base.i_module, prefix, pos, errors) if pmodule is None: return None if name not in pmodule.i_identities: err_add(errors, pos, 'TYPE_VALUE', (s, self.definition, 'identityref not found')) return None val = pmodule.i_identities[name] my_identity = self.base.i_identity vals = [] while True: if val == my_identity: return pmodule.i_identities[name] else: p = val.search_one('base') if p is None or p.i_identity is None: err_add(errors, pos, 'TYPE_VALUE', (s, self.definition, 'identityref not derived from %s' % \ my_identity.arg)) return None else: val = p.i_identity if val in vals: # circular; has been reported already return vals.append(val)
def add_module(self, ref, text, format=None, expect_modulename=None, expect_revision=None, expect_failure_error=True): """Parse a module text and add the module data to the context `ref` is a string which is used to identify the source of the text for the user. used in error messages `text` is the raw text data `format` is one of 'yang' or 'yin'. Returns the parsed and validated module on success, and None on error. """ if format == None: format = util.guess_format(text) if format == 'yin': p = yin_parser.YinParser() else: p = yang_parser.YangParser() module = p.parse(self, ref, text) if module is None: return None if expect_modulename is not None and expect_modulename != module.arg: if expect_failure_error: error.err_add(self.errors, module.pos, 'BAD_MODULE_NAME', (module.arg, ref, expect_modulename)) return None else: error.err_add(self.errors, module.pos, 'WBAD_MODULE_NAME', (module.arg, ref, expect_modulename)) if expect_revision is not None: latest_rev = util.get_latest_revision(module) if expect_revision != latest_rev: if expect_failure_error: error.err_add(self.errors, module.pos, 'BAD_REVISION', (latest_rev, ref, expect_revision)) return None else: error.err_add(self.errors, module.pos, 'WBAD_REVISION', (latest_rev, ref, expect_revision)) module.i_adler32 = zlib.adler32(text) return self.add_parsed_module(module)
def f(lostr, histr): try: if lostr in ['min', 'max']: lo = lostr else: lo = int(lostr) except ValueError: err_add(errors, stmt.pos, 'TYPE_VALUE', (lostr, '', 'not an integer')) return (None, None) try: if histr == '': # this means that a single number was in the length, e.g. # "4 | 5..6". return (lo, None) if histr in ['min', 'max']: hi = histr else: hi = int(histr) except ValueError: err_add(errors, stmt.pos, 'TYPE_VALUE', (histr, '', 'not an integer')) return None return (lo, hi)