| // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE.md file. |
| |
| /// Tools for loading and parsing platform-configuration files. |
| library platform_configuration; |
| |
| import "dart:async"; |
| |
| import "package:charcode/ascii.dart"; |
| |
| import "../compiler_new.dart" as api; |
| |
| /// Parses an Ini-like format. |
| /// |
| /// Sections are initialized with a name enclosed in brackets. |
| /// Each section contain zero or more properties of the form "name:value". |
| /// Empty lines are ignored. |
| /// Lines starting with # are ignored. |
| /// Duplicate names are not allowed. |
| /// All keys and values will be passed through [String.trim]. |
| /// |
| /// If an error is found, a [FormatException] is thrown, using [sourceUri] in |
| /// the error message. |
| /// |
| /// Example |
| /// ``` |
| /// [a] |
| /// b:c |
| /// |
| /// [d] |
| /// e:file:///tmp/bla |
| /// ``` |
| /// Will parse to {"a": {"b":"c"}, "d": {"e": "file:///tmp/bla"}}. |
| |
| Map<String, Map<String, String>> parseIni(List<int> source, |
| {Set<String> allowedSections, Uri sourceUri}) { |
| int startOfLine = 0; |
| int currentLine = 0; |
| |
| error(String message, int index) { |
| int column = index - startOfLine + 1; |
| throw new FormatException( |
| "$sourceUri:$currentLine:$column: $message", sourceUri, index); |
| } |
| |
| Map<String, Map<String, String>> result = |
| new Map<String, Map<String, String>>(); |
| Map<String, String> currentSection = null; |
| |
| if (source.length == 0) return result; |
| bool endOfFile = false; |
| |
| // Iterate once per $lf in file. |
| while (!endOfFile) { |
| currentLine += 1; |
| int endOfLine = source.indexOf($lf, startOfLine); |
| if (endOfLine == -1) { |
| // The dart2js provider adds a final 0 to the file. |
| endOfLine = source.last == 0 ? source.length - 1 : source.length; |
| endOfFile = true; |
| } |
| if (startOfLine != endOfLine) { |
| int firstChar = source[startOfLine]; |
| if (firstChar == $hash) { |
| // Comment, do nothing. |
| } else if (firstChar == $open_bracket) { |
| // Section header |
| int endOfHeader = source.indexOf($close_bracket, startOfLine); |
| if (endOfHeader == -1) { |
| error("'[' must be matched by ']' on the same line.", startOfLine); |
| } |
| if (endOfHeader == startOfLine + 1) { |
| error("Empty header name", startOfLine + 1); |
| } |
| if (endOfHeader != endOfLine - 1) { |
| error("Section heading lines must end with ']'", endOfHeader + 1); |
| } |
| int startOfSectionName = startOfLine + 1; |
| String sectionName = |
| new String.fromCharCodes(source, startOfSectionName, endOfHeader) |
| .trim(); |
| currentSection = new Map<String, String>(); |
| if (result.containsKey(sectionName)) { |
| error("Duplicate section name '$sectionName'", startOfSectionName); |
| } |
| if (allowedSections != null && !allowedSections.contains(sectionName)) { |
| error("Unrecognized section name '$sectionName'", startOfSectionName); |
| } |
| result[sectionName] = currentSection; |
| } else { |
| // Property line |
| if (currentSection == null) { |
| error("Property outside section", startOfLine); |
| } |
| int separator = source.indexOf($colon, startOfLine); |
| if (separator == startOfLine) { |
| error("Empty property name", startOfLine); |
| } |
| if (separator == -1 || separator > endOfLine) { |
| error("Property line without ':'", startOfLine); |
| } |
| String propertyName = |
| new String.fromCharCodes(source, startOfLine, separator).trim(); |
| if (currentSection.containsKey(propertyName)) { |
| error("Duplicate property name '$propertyName'", startOfLine); |
| } |
| String propertyValue = |
| new String.fromCharCodes(source, separator + 1, endOfLine).trim(); |
| currentSection[propertyName] = propertyValue; |
| } |
| } |
| startOfLine = endOfLine + 1; |
| } |
| return result; |
| } |
| |
| const String librariesSection = "libraries"; |
| const String dartSpecSection = "dart-spec"; |
| const String featuresSection = "features"; |
| |
| Map<String, Uri> libraryMappings( |
| Map<String, Map<String, String>> sections, Uri baseLocation) { |
| assert(sections.containsKey(librariesSection)); |
| Map<String, Uri> result = new Map<String, Uri>(); |
| sections[librariesSection].forEach((String name, String value) { |
| result[name] = baseLocation.resolve(value); |
| }); |
| return result; |
| } |
| |
| final Set<String> allowedSections = |
| new Set.from([librariesSection, dartSpecSection, featuresSection]); |
| |
| Future<Map<String, Uri>> load(Uri location, api.CompilerInput provider) { |
| return provider |
| .readFromUri(location, inputKind: api.InputKind.binary) |
| .then((api.Input input) { |
| return libraryMappings( |
| parseIni(input.data, |
| allowedSections: allowedSections, sourceUri: location), |
| location); |
| }); |
| } |