openc3/templates/tool_svelte/src/services/config-parser.js
/*
# Copyright 2022 Ball Aerospace & Technologies Corp.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# Modified by OpenC3, Inc.
# All changes Copyright 2024, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.
*/
export class ConfigParserError {
constructor(config_parser, message, usage = '', url = '') {
this.keyword = config_parser.keyword
this.parameters = config_parser.parameters
this.filename = config_parser.filename
this.line = config_parser.line
this.lineNumber = config_parser.lineNumber
this.message = message
this.usage = usage
this.url = url
}
}
export class ConfigParserService {
keyword = null
parameters = []
filename = ''
line = ''
lineNumber = 0
url = 'https://docs.openc3.com/docs'
constructor() {}
verify_num_parameters(min_num_params, max_num_params, usage = '') {
// This syntax works with 0 because each doesn't return any values
// for a backwards range
for (let index = 1; index <= min_num_params; index++) {
// If the parameter is nil (0 based) then we have a problem
if (this.parameters[index - 1] === undefined) {
throw new ConfigParserError(
this,
`Not enough parameters for ${this.keyword}.`,
usage,
this.url,
)
}
}
// If they pass null for max_params we don't check for a maximum number
if (max_num_params && this.parameters[max_num_params] !== undefined) {
throw new ConfigParserError(
this,
`Too many parameters for ${this.keyword}.`,
usage,
this.url,
)
}
}
remove_quotes(string) {
if (string.length < 2) {
return string
}
let first_char = string.charAt(0)
if (first_char !== '"' && first_char !== "'") {
return string
}
let last_char = string.charAt(string.length - 1)
if (first_char !== last_char) {
return string
}
return string.substring(1, string.length - 1)
}
scan_string(string, rx) {
if (!rx.global) throw "rx must have 'global' flag set"
let r = []
string.replace(rx, function (match) {
r.push(match)
return match
})
return r
}
parse_string(
input_string,
original_filename,
yield_non_keyword_lines,
remove_quotes,
handler,
) {
let string_concat = false
this.line = ''
this.keyword = null
this.parameters = []
this.filename = original_filename
// Break string into lines
let lines = input_string.split('\n')
let numLines = lines.length
for (let i = 0; i < numLines; i++) {
this.lineNumber = i + 1
let line = lines[i].trim()
// Ensure the line length is not 0
if (line.length === 0) {
continue
}
if (string_concat === true) {
// Skip comment lines after a string concatenation
if (line[0] === '#') {
continue
}
// Remove the opening quote if we're continuing the line
line = line.substring(1, line.length)
}
// Check for string continuation
let last_char = line.charAt(line.length - 1)
let newline = false
switch (last_char) {
case '+': // String concatenation with newlines
newline = true
// Deliberate fall through
case '\\': // String concatenation
// Trim off the concat character plus any spaces, e.g. "line" \
let trim = line.substring(0, line.length - 1).trim()
// Now trim off the last quote so it will flow into the next line
this.line += trim.substring(0, trim.length - 1)
if (newline) {
this.line += '\n'
}
string_concat = true
continue
case '&': // Line continuation
this.line += line.substring(0, line.length - 1)
continue
default:
this.line += line
}
string_concat = false
let rx = /("([^\\"]|\\.)*")|('([^\\']|\\.)*')|\S+/g
let data = this.scan_string(this.line, rx)
let first_item = ''
if (data.length > 0) {
first_item = first_item + data[0]
}
if (first_item.length === 0 || first_item.charAt(0) === '#') {
this.keyword = null
} else {
this.keyword = first_item.toUpperCase()
}
this.parameters = []
// Ignore lines without keywords: comments and blank lines
if (this.keyword === null) {
if (yield_non_keyword_lines) {
handler(this.keyword, this.parameters, this.line, this.lineNumber)
}
this.line = ''
continue
}
let length = data.length
if (length > 1) {
for (let index = 1; index < length; index++) {
let string = data[index]
// Don't process trailing comments such as:
// KEYWORD PARAM #This is a comment
if (string.length > 0 && string.charAt(0) === '#') {
break
}
if (remove_quotes) {
this.parameters.push(this.remove_quotes(string))
} else {
this.parameters.push(string)
}
}
}
handler(this.keyword, this.parameters, this.line, this.lineNumber)
this.line = ''
} // for all the lines
} // parse_string
} // class ConfigParserService