<commit_before># Yang - evaluable schema expression

This module provides support for basic set of YANG schema modeling
language by using the built-in *extension* syntax to define additional
schema language constructs. The actual YANG language [RFC
6020](http://tools.ietf.org/html/rfc6020) specifications are loaded
inside the [main module](./main.coffee).

This module is the **primary interface** for consumers of this
library.

## Dependencies
 
    debug  = require('debug')('yang:schema')
    parser = require 'yang-parser'
    indent = require 'indent-string'

    Expression = require './expression'
    Extension = require './extension'
    XPath = require './xpath'

## Class Yang

    class Yang extends Expression
      @scope:
        extension: '0..n'
        typedef:   '0..n'
        module:    '0..n'
        submodule: '0..n'

## Class-level methods

      @clear: ->
        @module.splice(0,@module.length) if @module?
        @submodule.splice(0,@submodule.length) if @submodule?

### parse (schema)

This class-level routine performs recursive parsing of passed in
statement and sub-statements. It provides syntactic, semantic and
contextual validations on the provided schema and returns the final JS
object tree structure as hierarchical Yang expression instances.

If any validation errors are encountered, it will throw the
appropriate error along with the context information regarding the
error.

      @parse: (schema, opts={}) ->
        return schema if schema instanceof Yang
        opts.compile ?= true
        try
          schema = parser.parse schema if typeof schema is 'string'
        catch e
          e.offset = 50 unless e.offset > 50
          offender = schema.slice e.offset-50, e.offset+50
          offender = offender.replace /\s\s+/g, ' '
          throw @error "invalid YANG syntax detected around: '#{offender}'", offender

        unless schema instanceof Object
          throw @error "must pass in valid YANG schema", schema

        kind = switch
          when !!schema.prf then "#{schema.prf}:#{schema.kw}"
          else schema.kw
        tag = schema.arg unless schema.arg is false

        yang = (new this kind, tag).extends schema.substmts.map (x) => @parse x, compile: (x.kw is 'extension')
        
        # perform final scoped constraint validation
        for kind, constraint of yang.scope when constraint in [ '1', '1..n' ]
          unless yang.hasOwnProperty kind
            throw yang.error "constraint violation for required '#{kind}' = #{constraint}"
        yang.compile() if opts.compile
        return yang

For comprehensive overview on currently supported YANG statements,
please refer to
[Compliance Report](../test/yang-compliance-coverage.md) for the latest
[RFC 6020](http://tools.ietf.org/html/rfc6020) YANG specification
compliance.

### compose (data [, opts={}])

This call *accepts* any arbitrary JS object and it will attempt to
convert it into a structural `Yang` expression instance. It will
analyze the passed in JS data and perform best match mapping to an
appropriate YANG schema representation to describe the input
data. This method will not be able to determine conditionals or any
meta-data to further constrain the data, but it should provide a good
starting point with the resulting `Yang` expression instance.

      @compose: (data, opts={}) ->
        unless data?
          throw @error "must supply input 'data' to compose"

        # explict compose
        if opts.kind?
          ext = Yang::lookup.call this, 'extension', opts.kind
          unless ext instanceof Expression
            throw @error "unable to find requested '#{opts.kind}' extension"
          return ext.compose? data, opts

        # implicit compose (dynamic discovery)
        for ext in @extension when ext.compose instanceof Function
          debug "checking data if #{ext.tag}"
          res = ext.compose data, opts
          return res if res instanceof Yang

This facility is a powerful construct to dynamically generate `Yang`
schema from ordinary JS objects. For additional usage examples, please
refer to [Dynamic Composition](../TUTORIAL.md#dynamic-composition)
section in the [Getting Started Guide](../TUTORIAL.md).

## Main constructor

The constructor inherits from `Expression` which returns a `bound
function` that invokes [eval](#eval-data-opts) when called.

      @property 'source',
        get: ->
          @state.source ?= @lookup 'extension', @kind
          @state.source or {}
        set: (value) -> @state.source = value
        
      @property 'datakey',
        get: -> switch
          when @parent instanceof Yang and @parent.kind is 'module'
            "#{@parent.tag}:#{@tag}"
          when @parent instanceof Yang and @parent.kind is 'submodule'
            "#{@parent['belongs-to'].tag}:#{@tag}"
          when @node and @external and not @state.relative
            "#{@origin.root.tag}:#{@tag}"
          else @tag ? @kind

      @property 'external',
        get: -> @origin? and @origin.root isnt @root and @origin.root.kind is 'module'
            
      @property 'datapath',
        get: ->
          return '' if @kind is 'module'
          label = if @node then @datakey else "#{@kind}(#{@datakey})"
          return switch
            when @parent?.datapath? then [ @parent.datapath, label ].join('/')
            else label
                  
      debug: -> debug "[#{@uri}]", arguments...
      emit: (event, args...) ->
        @emitter.emit arguments...
        @root.emit event, this if event is 'change' and this isnt @root

## Instance-level methods

### compile (obj)

      compile: ->
        unless @source instanceof Expression
          @debug @source
          throw @error "encountered unknown extension '#{@kind}'"
        super arguments...

### bind (obj)

Every instance of `Yang` expression can be *bound* with control logic
which will be used during [eval](#eval-data-opts) to produce schema
infused **adaptive data object**. This routine is *inherited* from
[Class Expression](./expression.coffee).

This facility can be used to associate default behaviors for any
element in the configuration tree, as well as handler logic for
various YANG statements such as *rpc*, *feature*, etc.

This call will return the original `Yang` expression instance with the
new bindings registered within the `Yang` expression hierarchy.

      bind: (data) ->
        if not @data and @nodes.length > 0 and typeof data is 'object'
          for key, binding of data
            try @locate(key).bind binding
            catch e
              throw e if e.name is 'ExpressionError'
              throw @error "failed to bind to '#{key}' (schema-path not found)", e
        else
          super data
        return this

Please refer to [Schema Binding](../TUTORIAL.md#schema-binding)
section of the [Getting Started Guide](../TUTORIAL.md) for usage
examples.

### eval (data, opts={})

Every instance of `Yang` expression can be [eval](#eval-data-opts)
with arbitrary JS data input which will apply the schema against the
provided data and return a schema infused **adaptive** data object.

This is an extremely useful construct which brings out the true power
of YANG for defining and governing arbitrary JS data structures.<commit_msg>fix issue with binding on module schema without any data nodes<commit_after># Yang - evaluable schema expression

This module provides support for basic set of YANG schema modeling
language by using the built-in *extension* syntax to define additional
schema language constructs. The actual YANG language [RFC
6020](http://tools.ietf.org/html/rfc6020) specifications are loaded
inside the [main module](./main.coffee).

This module is the **primary interface** for consumers of this
library.

## Dependencies
 
    debug  = require('debug')('yang:schema')
    parser = require 'yang-parser'
    indent = require 'indent-string'

    Expression = require './expression'
    Extension = require './extension'
    XPath = require './xpath'

## Class Yang

    class Yang extends Expression
      @scope:
        extension: '0..n'
        typedef:   '0..n'
        module:    '0..n'
        submodule: '0..n'

## Class-level methods

      @clear: ->
        @module.splice(0,@module.length) if @module?
        @submodule.splice(0,@submodule.length) if @submodule?

### parse (schema)

This class-level routine performs recursive parsing of passed in
statement and sub-statements. It provides syntactic, semantic and
contextual validations on the provided schema and returns the final JS
object tree structure as hierarchical Yang expression instances.

If any validation errors are encountered, it will throw the
appropriate error along with the context information regarding the
error.

      @parse: (schema, opts={}) ->
        return schema if schema instanceof Yang
        opts.compile ?= true
        try
          schema = parser.parse schema if typeof schema is 'string'
        catch e
          e.offset = 50 unless e.offset > 50
          offender = schema.slice e.offset-50, e.offset+50
          offender = offender.replace /\s\s+/g, ' '
          throw @error "invalid YANG syntax detected around: '#{offender}'", offender

        unless schema instanceof Object
          throw @error "must pass in valid YANG schema", schema

        kind = switch
          when !!schema.prf then "#{schema.prf}:#{schema.kw}"
          else schema.kw
        tag = schema.arg unless schema.arg is false

        yang = (new this kind, tag).extends schema.substmts.map (x) => @parse x, compile: (x.kw is 'extension')
        
        # perform final scoped constraint validation
        for kind, constraint of yang.scope when constraint in [ '1', '1..n' ]
          unless yang.hasOwnProperty kind
            throw yang.error "constraint violation for required '#{kind}' = #{constraint}"
        yang.compile() if opts.compile
        return yang

For comprehensive overview on currently supported YANG statements,
please refer to
[Compliance Report](../test/yang-compliance-coverage.md) for the latest
[RFC 6020](http://tools.ietf.org/html/rfc6020) YANG specification
compliance.

### compose (data [, opts={}])

This call *accepts* any arbitrary JS object and it will attempt to
convert it into a structural `Yang` expression instance. It will
analyze the passed in JS data and perform best match mapping to an
appropriate YANG schema representation to describe the input
data. This method will not be able to determine conditionals or any
meta-data to further constrain the data, but it should provide a good
starting point with the resulting `Yang` expression instance.

      @compose: (data, opts={}) ->
        unless data?
          throw @error "must supply input 'data' to compose"

        # explict compose
        if opts.kind?
          ext = Yang::lookup.call this, 'extension', opts.kind
          unless ext instanceof Expression
            throw @error "unable to find requested '#{opts.kind}' extension"
          return ext.compose? data, opts

        # implicit compose (dynamic discovery)
        for ext in @extension when ext.compose instanceof Function
          debug "checking data if #{ext.tag}"
          res = ext.compose data, opts
          return res if res instanceof Yang

This facility is a powerful construct to dynamically generate `Yang`
schema from ordinary JS objects. For additional usage examples, please
refer to [Dynamic Composition](../TUTORIAL.md#dynamic-composition)
section in the [Getting Started Guide](../TUTORIAL.md).

## Main constructor

The constructor inherits from `Expression` which returns a `bound
function` that invokes [eval](#eval-data-opts) when called.

      @property 'source',
        get: ->
          @state.source ?= @lookup 'extension', @kind
          @state.source or {}
        set: (value) -> @state.source = value
        
      @property 'datakey',
        get: -> switch
          when @parent instanceof Yang and @parent.kind is 'module'
            "#{@parent.tag}:#{@tag}"
          when @parent instanceof Yang and @parent.kind is 'submodule'
            "#{@parent['belongs-to'].tag}:#{@tag}"
          when @node and @external and not @state.relative
            "#{@origin.root.tag}:#{@tag}"
          else @tag ? @kind

      @property 'external',
        get: -> @origin? and @origin.root isnt @root and @origin.root.kind is 'module'
            
      @property 'datapath',
        get: ->
          return '' if @kind is 'module'
          label = if @node then @datakey else "#{@kind}(#{@datakey})"
          return switch
            when @parent?.datapath? then [ @parent.datapath, label ].join('/')
            else label
                  
      debug: -> debug "[#{@uri}]", arguments...
      emit: (event, args...) ->
        @emitter.emit arguments...
        @root.emit event, this if event is 'change' and this isnt @root

## Instance-level methods

### compile (obj)

      compile: ->
        unless @source instanceof Expression
          @debug @source
          throw @error "encountered unknown extension '#{@kind}'"
        super arguments...

### bind (obj)

Every instance of `Yang` expression can be *bound* with control logic
which will be used during [eval](#eval-data-opts) to produce schema
infused **adaptive data object**. This routine is *inherited* from
[Class Expression](./expression.coffee).

This facility can be used to associate default behaviors for any
element in the configuration tree, as well as handler logic for
various YANG statements such as *rpc*, *feature*, etc.

This call will return the original `Yang` expression instance with the
new bindings registered within the `Yang` expression hierarchy.

      bind: (data) ->
        if not @data and (@kind is 'module' or @nodes.length > 0) and typeof data is 'object'
          for key, binding of data
            try @locate(key).bind binding
            catch e
              throw e if e.name is 'ExpressionError'
              throw @error "failed to bind to '#{key}' (schema-path not found)", e
        else
          super data
        return this

Please refer to [Schema Binding](../TUTORIAL.md#schema-binding)
section of the [Getting Started Guide](../TUTORIAL.md) for usage
examples.

### eval (data, opts={})

Every instance of `Yang` expression can be [eval](#eval-data-opts)
with arbitrary JS data input which will apply the schema against the
provided data and return a schema infused **adaptive** data object.

This is an extremely useful construct which brings out the true power
of YANG for defining and governing arbitrary JS data structures.
{'fraction_non_alphanumeric': 0.09839989059080963, 'fraction_numerical': 0.004513129102844639, 'mean_word_length': 2.760606839804577, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7347605', 'n_tokens_mistral': 3926, 'n_tokens_neox': 3763, 'n_words': 1605}