ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

DSL Configuration Parser with Validation

Groovy

Goal -- WPM

Ready
Exercise Algorithm Area
1import java.util.ArrayList
2import java.util.HashMap
3import java.util.List
4import java.util.Map
5import java.util.regex.Matcher
6import java.util.regex.Pattern
7
8class DslConfigParser {
9
10private static final Pattern KEY_VALUE_PAIR_PATTERN = Pattern.compile("\s*(\w+)\s*:\s*(.*?)(?:,|\n|$)")
11private static final Pattern BLOCK_START_PATTERN = Pattern.compile("\s*(\w+)\s*\{\s*$")
12private static final Pattern BLOCK_END_PATTERN = Pattern.compile("\s*\}\s*$")
13
14private static final Map<String, Class<?>> VALID_TYPES = [
15"string": String.class,
16"integer": Integer.class,
17"boolean": Boolean.class,
18"list": List.class
19]
20
21/**
22* Parses a DSL configuration string into a structured map.
23* @param dslContent The DSL configuration content.
24* @return A map representing the parsed configuration.
25* @throws IllegalArgumentException if parsing fails or structure is invalid.
26*/
27Map<String, Object> parse(String dslContent) {
28if (dslContent == null || dslContent.trim().isEmpty()) {
29return [:]
30}
31// Basic validation: Ensure balanced braces
32if (countOccurrences(dslContent, '{') != countOccurrences(dslContent, '}')) {
33throw new IllegalArgumentException("Mismatched braces in DSL content.")
34}
35
36// For simplicity, we'll use a simplified recursive descent approach.
37// A real parser might use a lexer/parser generator.
38return parseBlock(dslContent, 0)
39}
40
41/**
42* Recursively parses a block of DSL content.
43* @param content The content to parse.
44* @param startIndex The starting index within the content.
45* @return A map representing the parsed block and the new index.
46* @throws IllegalArgumentException if parsing fails.
47*/
48private Map<String, Object> parseBlock(String content, int startIndex) {
49Map<String, Object> blockData = new HashMap<>()
50int currentIndex = startIndex
51
52while (currentIndex < content.length()) {
53Matcher blockStartMatcher = BLOCK_START_PATTERN.matcher(content)
54blockStartMatcher.region(currentIndex, content.length())
55if (blockStartMatcher.lookingAt()) {
56String blockName = blockStartMatcher.group(1)
57int blockContentStart = blockStartMatcher.end()
58int blockEndIndex = findMatchingBrace(content, blockContentStart)
59if (blockEndIndex == -1) {
60throw new IllegalArgumentException("Unclosed block starting at index ${currentIndex}.")
61}
62String subBlockContent = content.substring(blockContentStart, blockEndIndex)
63blockData.put(blockName, parseBlock(subBlockContent, 0))
64currentIndex = blockEndIndex + 1
65} else {
66Matcher keyValueMatcher = KEY_VALUE_PAIR_PATTERN.matcher(content)
67keyValueMatcher.region(currentIndex, content.length())
68if (keyValueMatcher.lookingAt()) {
69String key = keyValueMatcher.group(1)
70String valueStr = keyValueMatcher.group(2).trim()
71// Attempt to parse value based on context or type inference
72blockData.put(key, parseValue(valueStr))
73currentIndex = keyValueMatcher.end()
74} else if (BLOCK_END_PATTERN.matcher(content).region(currentIndex, content.length()).lookingAt()) {
75// End of current block, return
76break
77} else if (Character.isWhitespace(content.charAt(currentIndex))) {
78// Skip whitespace
79currentIndex++
80} else {
81throw new IllegalArgumentException("Unexpected token at index ${currentIndex}: ${content.substring(currentIndex, Math.min(currentIndex + 10, content.length()))}")
82}
83}
84}
85return blockData
86}
87
88/**
89* Parses a string value into its appropriate type (String, Integer, Boolean).
90* @param valueStr The string representation of the value.
91* @return The parsed value object.
92*/
93private Object parseValue(String valueStr) {
94if (valueStr.equalsIgnoreCase("true")) return Boolean.TRUE
95if (valueStr.equalsIgnoreCase("false")) return Boolean.FALSE
96try {
97return Integer.parseInt(valueStr)
98} catch (NumberFormatException e) {
99// It's a string, remove potential quotes
100if (valueStr.startsWith("'") && valueStr.endsWith("'")) {
101return valueStr.substring(1, valueStr.length() - 1)
102}
103if (valueStr.startsWith("\"") && valueStr.endsWith("\"")) {
104return valueStr.substring(1, valueStr.length() - 1)
105}
106return valueStr // Return as string if no quotes and not a number
107}
108}
109
110/**
111* Finds the index of the matching closing brace for a given opening brace.
112* @param content The string to search within.
113* @param startIndex The index of the opening brace.
114* @return The index of the matching closing brace, or -1 if not found.
115*/
116private int findMatchingBrace(String content, int startIndex) {
117int braceLevel = 1
118for (int i = startIndex; i < content.length(); i++) {
119char c = content.charAt(i)
120if (c == '{') {
121braceLevel++
122} else if (c == '}') {
123braceLevel--
124if (braceLevel == 0) {
125return i
126}
127}
128}
129return -1
130}
131
132/**
133* Counts occurrences of a character in a string.
134* @param text The string to search.
135* @param charToCount The character to count.
136* @return The number of occurrences.
137*/
138private int countOccurrences(String text, char charToCount) {
139int count = 0
140for (int i = 0; i < text.length(); i++) {
141if (text.charAt(i) == charToCount) {
142count++
143}
144}
145return count
146}
147
148/**
149* Validates the parsed configuration against a schema.
150* @param config The parsed configuration map.
151* @param schema The validation schema map.
152* @throws IllegalArgumentException if validation fails.
153*/
154void validate(Map<String, Object> config, Map<String, Object> schema) {
155schema.each {
156schemaName, schemaDefinition ->
157if (schemaDefinition instanceof Map) { // Nested block
158if (!config.containsKey(schemaName)) {
159throw new IllegalArgumentException("Missing required block: ${schemaName}")
160}
161Object configValue = config.get(schemaName)
162if (!(configValue instanceof Map)) {
163throw new IllegalArgumentException("Expected block '${schemaName}' to be a map, but found ${configValue.class.simpleName}.")
164}
165validate(configValue as Map<String, Object>, schemaDefinition as Map<String, Object>)
166} else if (schemaDefinition instanceof String) { // Simple type validation
167if (!config.containsKey(schemaName)) {
168throw new IllegalArgumentException("Missing required field: ${schemaName}")
169}
170Object configValue = config.get(schemaName)
171Class<?> expectedType = VALID_TYPES.get(schemaDefinition)
172if (expectedType == null) {
173throw new IllegalArgumentException("Unknown type in schema: ${schemaDefinition}")
174}
175if (!expectedType.isInstance(configValue)) {
176throw new IllegalArgumentException("Field '${schemaName}' expected type ${schemaDefinition}, but found ${configValue.class.simpleName}.")
177}
178} else {
179throw new IllegalArgumentException("Invalid schema definition for '${schemaName}'. Must be String (type) or Map (nested schema).")
180}
181}
182}
183
184/**
185* Main method for demonstration.
186*/
187static void main(String[] args) {
188DslConfigParser parser = new DslConfigParser()
189
190String dsl1 = """
191server {
192host: 'localhost',
193port: 8080,
194sslEnabled: false
195}
196database {
197type: 'postgresql',
198connectionString: "jdbc:postgresql://db.example.com/mydb",
199poolSize: 10
200}
201"""
202
203Map<String, Object> schema1 = [
204"server": [
205"host": "string",
206"port": "integer",
207"sslEnabled": "boolean"
208],
209"database": [
210"type": "string",
211"connectionString": "string",
212"poolSize": "integer"
213]
214]
215
216try {
217Map<String, Object> parsedConfig1 = parser.parse(dsl1)
218println "Parsed Config 1: ${parsedConfig1}"
219parser.validate(parsedConfig1, schema1)
220println "Validation successful for Config 1."
221} catch (IllegalArgumentException e) {
222println "Error processing Config 1: ${e.getMessage()}"
223}
224
225println "\n---\n\n"
226
227String dsl2 = """
228logging {
229level: 'INFO',
230file: "app.log"
231}
232users: [
233name: 'admin',
234id: 123
235] // This is an invalid structure for a list
236"""
237
238Map<String, Object> schema2 = [
239"logging": [
240"level": "string",
241"file": "string"
242],
243"users": "list" // Expecting a list, but DSL structure is wrong
244]
245
246try {
247Map<String, Object> parsedConfig2 = parser.parse(dsl2)
248println "Parsed Config 2: ${parsedConfig2}"
249parser.validate(parsedConfig2, schema2)
250println "Validation successful for Config 2."
251} catch (IllegalArgumentException e) {
252println "Error processing Config 2: ${e.getMessage()}"
253}
254
255println "\n---\n\n"
256
257String dsl3 = """
258settings {
259timeout: 30
260}
261features {
262featureA: true
263}
264"""
265
266Map<String, Object> schema3 = [
267"settings": [
268"timeout": "integer"
269],
270"features": [
271"featureA": "boolean",
272"featureB": "boolean" // Missing in DSL
273]
274]
275
276try {
277Map<String, Object> parsedConfig3 = parser.parse(dsl3)
278println "Parsed Config 3: ${parsedConfig3}"
279parser.validate(parsedConfig3, schema3)
280println "Validation successful for Config 3."
281} catch (IllegalArgumentException e) {
282println "Error processing Config 3: ${e.getMessage()}"
283}
284}
285}
Algorithm description viewbox

DSL Configuration Parser with Validation

Algorithm description:

This Groovy code implements a parser for a custom configuration DSL. It takes a string representing the DSL configuration, which can include nested blocks and key-value pairs, and transforms it into a nested map structure. Crucially, it also includes a validation step that checks the parsed structure against a predefined schema, ensuring that required fields are present and have the correct data types. This is essential for robust configuration management in applications.

Algorithm explanation:

The `DslConfigParser` class uses regular expressions to identify key-value pairs and block structures (`{}`) within the DSL content. The `parse` method acts as the entry point, performing a basic check for balanced braces and then calling `parseBlock` to handle the recursive parsing. `parseBlock` iterates through the content, identifying and recursively parsing nested blocks or parsing key-value pairs. `parseValue` attempts to infer the type of a value (boolean, integer, or string). The `findMatchingBrace` helper is critical for correctly identifying the boundaries of nested blocks. After parsing, the `validate` method takes the parsed map and a schema map. It recursively traverses both structures, checking for the presence of required keys and verifying that the data types of the parsed values match the types specified in the schema. Error handling is done via `IllegalArgumentException`. The time complexity is roughly O(N) where N is the length of the DSL content, as each character is processed a constant number of times. Space complexity is O(D) where D is the depth of nesting, due to recursion stack and map storage.

Pseudocode:

CLASS DslConfigParser:
  // Regex patterns for key-value, block start/end
  // Map of valid type names to Class objects

  FUNCTION parse(dslContent):
    IF dslContent is null OR empty THEN RETURN empty map
    IF count('{') != count('}') THEN THROW error "Mismatched braces"
    RETURN parseBlock(dslContent, 0)
  END FUNCTION

  FUNCTION parseBlock(content, startIndex):
    blockData = new map
    currentIndex = startIndex

    WHILE currentIndex < length(content):
      IF content matches BLOCK_START_PATTERN at currentIndex:
        blockName = captured group 1
        blockContentStart = matcher.end()
        blockEndIndex = findMatchingBrace(content, blockContentStart)
        IF blockEndIndex == -1 THEN THROW error "Unclosed block"
        subBlockContent = substring(content, blockContentStart, blockEndIndex)
        blockData[blockName] = parseBlock(subBlockContent, 0)
        currentIndex = blockEndIndex + 1
      ELSE IF content matches KEY_VALUE_PAIR_PATTERN at currentIndex:
        key = captured group 1
        valueStr = captured group 2.trim()
        blockData[key] = parseValue(valueStr)
        currentIndex = matcher.end()
      ELSE IF content matches BLOCK_END_PATTERN at currentIndex:
        BREAK // End of current block
      ELSE IF isWhitespace(content[currentIndex]):
        currentIndex++
      ELSE:
        THROW error "Unexpected token"
      END IF
    END WHILE
    RETURN blockData
  END FUNCTION

  FUNCTION parseValue(valueStr):
    IF valueStr is "true" (case-insensitive) THEN RETURN true
    IF valueStr is "false" (case-insensitive) THEN RETURN false
    TRY:
      RETURN Integer.parseInt(valueStr)
    CATCH NumberFormatException:
      // Remove quotes if present, otherwise return as string
      RETURN valueStr without surrounding quotes
    END TRY
  END FUNCTION

  FUNCTION findMatchingBrace(content, startIndex):
    // Logic to find matching brace, handling nested braces
    // ... returns index or -1 ...
  END FUNCTION

  FUNCTION countOccurrences(text, charToCount):
    // Counts character occurrences
    // ... returns count ...
  END FUNCTION

  FUNCTION validate(config, schema):
    FOR EACH schemaName, schemaDefinition IN schema:
      IF schemaDefinition is a Map:
        IF config does not contain schemaName THEN THROW error "Missing block"
        configValue = config[schemaName]
        IF configValue is not a Map THEN THROW error "Expected map"
        validate(configValue, schemaDefinition)
      ELSE IF schemaDefinition is a String:
        IF config does not contain schemaName THEN THROW error "Missing field"
        configValue = config[schemaName]
        expectedType = VALID_TYPES[schemaDefinition]
        IF expectedType is null THEN THROW error "Unknown schema type"
        IF configValue is not an instance of expectedType THEN THROW error "Type mismatch"
      ELSE:
        THROW error "Invalid schema definition"
      END IF
    END FOR
  END FUNCTION

  FUNCTION main(args):
    // ... (demonstration code) ...
  END FUNCTION
END CLASS