UNPKG

@litexa/core

Version:

Litexa, a programming language for writing Alexa skills

197 lines (151 loc) 5.97 kB
### # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ### lib = module.exports.lib = {} { ParserError, formatLocationStart } = require("./errors.coffee").lib compileSlot = (context, type) -> code = context.skill.getFileContents type.filename, context.language code = code.js ? code unless code throw new ParserError null, "Couldn't find contents of file #{type.filename} to build slot type" exports = {} try eval code catch err throw new ParserError null, "While compiling #{type.filename}: #{err}" unless (k for k of exports).length > 0 throw new ParserError null, "Slot type builder file #{type.filename} does not appear to export any slot building functions" unless type.functionName of exports throw new ParserError null, "Slot type builder #{type.functionName} not found in #{type.filename}, saw these functions in there [#{(k for k of exports).join(',')}]" try data = exports[type.functionName](context.skill, context.language) catch err throw new ParserError null, "While building #{type.functionName} from #{type.filename}: #{err}" unless typeof(data) == 'object' throw new ParserError null, "Slot builder #{type.functionName} returned #{JSON.stringify data}, expected an object in the form { name:"", values:[] }" for key in ['name', 'values'] unless key of data throw new ParserError null, "Missing key `#{key}` in result of slot builder #{type.functionName}: #{JSON.stringify data}" for value, index in data.values if typeof(value) == 'string' data.values[index] = id: undefined name: { value: value synonyms: [] } if data.name of context.types throw new ParserError null, "Duplicate slot type definition found for name `#{data.name}`. Please remove one." context.types[data.name] = data return data.name createSlotFromArray = (context, slotName, values) -> typeName = "#{slotName}Type" if typeName of context.types throw new ParserError null, "Duplicate slot type definition found for name `#{typeName}` while creating implicit type for slot `#{slotName}`. Please remove conflicting definitions." type = name: typeName values: [] for v in values type.values.push id: undefined name: { value: JSON.parse v synonyms: [] } if context? context.types[typeName] = type return typeName class lib.Slot constructor: (@name) -> setType: (location, type) -> if @type? if @type.filename? and @type.functionName? throw new ParserError location, "The slot named `#{@name}` already has a defined type from the slot builder: `#{@type.filename}:#{@type.functionName}`" else throw new ParserError location, "The slot named `#{@name}` already has a defined type: `#{@type}`" @type = type @typeLocation = location if typeof(@type) == 'string' @builtinType = @type.indexOf('AMAZON.') == 0 collectDefinedSlotTypes: (context, customSlotTypes) -> unless @type? throw new ParserError null, "the slot named `#{@name}` doesn't have a 'with' statement defining its type" if Array.isArray @type typeName = createSlotFromArray(context, @name.toString(), @type) customSlotTypes.push typeName else if @type.isFileFunctionReference typeName = compileSlot(context, @type) customSlotTypes.push typeName validateSlotTypes: (customSlotTypes) -> # @TODO: Validate built in types? Maybe just a warning? if typeof(@type) == 'string' and not @builtinType unless @type in customSlotTypes throw new ParserError @typeLocation, "the slot type named `#{@type}` is not defined anywhere" toModelV2: (context, slots) -> unless @type? throw new ParserError null, "missing type for slot `#{@name}`" if Array.isArray @type slots.push { name: @name.toString() type: createSlotFromArray(context, @name.toString(), @type) } else if @type.isFileFunctionReference slots.push { name: @name.toString() type: compileSlot(context, @type) } else slots.push { name: @name.toString() type: @type } class lib.Utterance constructor: (@location, @root) -> unless @root? throw new ParserError @location, "Utterance constructed with no part (this shouldn't happen, contact a member of the litexa dev team!)" isUtterance: true visit: (depth, fn) -> @root.visit (depth+1), fn toString: -> return @root.toString() toUtterance: -> # we can trim excess white space from the full utterances since # it doesn't actually count for anything in the model, and this way # you can use as much or as little white space as makes for the best # legibility in the expression utterances = @root.toUtterance() utterances = for u in utterances u.trim().replace( /\s+/g, ' ' ) return utterances toModelV2: -> return @toUtterance() parse: (line) -> unless @regex? @testScore = @root.toTestScore?() ? 0 regexText = @root.toRegex?() ? "ROOT_UNSUPPORTED" regexText = "^#{regexText}$" @regex = new RegExp(regexText, 'i') match = @regex.exec(line) return [null, null] unless match? # this is a map of slot name -> slot value slots = {} if match.groups? # note, this relies on named captures, which means we're at minimum v10 of node now slots[k] = v for k, v of match.groups return [@testScore, slots] isEquivalentTo: (otherUtterance) -> return JSON.stringify(otherUtterance.toUtterance()) == JSON.stringify(@toUtterance()) isUtterance: true