Commit 51b85c30 authored by David Owens II's avatar David Owens II

Moved atkpkgparser into just atpkg to unify the codebase.

parents
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
public enum ParseError: ErrorType {
case InvalidPackageFile
case ExpectedTokenType(TokenType, Token?)
case InvalidTokenForValueType(Token?)
}
public enum ParseValue {
case StringLiteral(String)
case IntegerLiteral(Int)
case FloatLiteral(Double)
case BoolLiteral(Bool)
case Map([String:ParseValue])
case Vector([ParseValue])
}
public class ParseType {
public var name: String = ""
public var properties: [String:ParseValue] = [:]
}
public class Parser {
let lexer: Lexer
private func next() -> Token? {
while true {
guard let token = lexer.next() else { return nil }
if token.type != .Comment && token.type != .Terminal {
return lexer.peek()
}
}
}
public init?(filepath: String) {
guard let content = try? NSString(contentsOfFile: filepath, encoding: NSUTF8StringEncoding) else {
return nil
}
let scanner = Scanner(content: content as String)
self.lexer = Lexer(scanner: scanner)
}
public func parse() throws -> ParseType {
guard let token = next() else { throw ParseError.InvalidPackageFile }
if token.type == .OpenParen {
return try parseType()
}
else {
throw ParseError.ExpectedTokenType(.OpenParen, token)
}
}
private func parseType() throws -> ParseType {
let type = ParseType()
type.name = try parseIdentifier()
type.properties = try parseKeyValuePairs()
return type
}
private func parseKeyValuePairs() throws -> [String:ParseValue] {
var pairs: [String:ParseValue] = [:]
while let token = next() where token.type != .CloseParen && token.type != .CloseBrace {
lexer.stall()
let key = try parseKey()
let value = try parseValue()
pairs[key] = value
}
lexer.stall()
return pairs
}
private func parseKey() throws -> String {
let colon = next()
if colon?.type != .Colon { throw ParseError.ExpectedTokenType(.Colon, lexer.peek()) }
return try parseIdentifier()
}
private func parseIdentifier() throws -> String {
guard let identifier = next() else { throw ParseError.ExpectedTokenType(.Identifier, lexer.peek()) }
if identifier.type != .Identifier { throw ParseError.ExpectedTokenType(.Identifier, lexer.peek()) }
return identifier.value
}
private func parseValue() throws -> ParseValue {
guard let token = next() else { throw ParseError.InvalidTokenForValueType(nil) }
switch token.type {
case .OpenBrace: lexer.stall(); return try parseMap()
case .OpenBracket: lexer.stall(); return try parseVector()
case .StringLiteral: return .StringLiteral(token.value)
case .Identifier where token.value == "true": return .BoolLiteral(true)
case .Identifier where token.value == "false": return .BoolLiteral(false)
default: throw ParseError.InvalidTokenForValueType(token)
}
}
private func parseVector() throws -> ParseValue {
if let token = next() where token.type != .OpenBracket { throw ParseError.ExpectedTokenType(.OpenBracket, token) }
var items: [ParseValue] = []
while let token = next() where token.type != .CloseBracket {
lexer.stall()
items.append(try parseValue())
}
lexer.stall()
if let token = next() where token.type != .CloseBracket { throw ParseError.ExpectedTokenType(.CloseBracket, token) }
return .Vector(items)
}
private func parseMap() throws -> ParseValue {
if let token = next() where token.type != .OpenBrace { throw ParseError.ExpectedTokenType(.OpenBrace, token) }
let items = try parseKeyValuePairs()
if let token = next() where token.type != .CloseBrace { throw ParseError.ExpectedTokenType(.CloseBrace, token) }
return .Map(items)
}
}
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
public struct ScannerInfo {
public let character: Character?
public let line: Int
public let column: Int
}
public class Scanner {
var content: String
var index: String.Index
var current: ScannerInfo? = nil
private var shouldStall = false
var line: Int = 0
var column: Int = 0
public init(content: String) {
self.content = content
self.index = content.startIndex
self._defaults()
}
func _defaults() {
self.index = content.startIndex
self.line = 0
self.column = 0
self.shouldStall = false
self.current = nil
}
public func stall() {
shouldStall = true
}
public func next() -> ScannerInfo? {
if shouldStall {
shouldStall = false
return current
}
if index == content.endIndex {
current = nil
}
else {
current = ScannerInfo(character: content[index], line: line, column: column)
index = index.successor()
if current?.character == "\n" {
line += 1
column = 0
}
else {
column += 1
}
}
return current
}
public func peek() -> ScannerInfo? {
return current
}
}
\ No newline at end of file
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
public enum TokenType {
case Identifier
case OpenParen
case CloseParen
case OpenBracket
case CloseBracket
case OpenBrace
case CloseBrace
case StringLiteral
case Terminal
case Colon
case Comment
case Unknown
case EOF
}
public func ==(lhs: Token, rhs: Token) -> Bool {
return lhs.type == rhs.type &&
lhs.line == rhs.line &&
lhs.column == rhs.column &&
lhs.value == rhs.value
}
public struct Token: Equatable {
public let value: String
public let line: Int
public let column: Int
public let type: TokenType
public init(type: TokenType, value: String = "", line: Int = 0, column: Int = 0) {
self.type = type
self.value = value
self.line = line
self.column = column
}
}
func isCharacterPartOfSet(c: Character?, set: NSCharacterSet) -> Bool {
guard let c = c else { return false }
var isMember = true
for utf16Component in String(c).utf16 {
if !set.characterIsMember(utf16Component) { isMember = false; break }
}
return isMember
}
func isValidIdentifierSignalCharacter(c: Character?) -> Bool {
return isCharacterPartOfSet(c, set: NSCharacterSet.letterCharacterSet())
}
func isValidIdenitifierCharacter(c: Character?) -> Bool {
return isCharacterPartOfSet(c, set: NSCharacterSet.letterCharacterSet()) || c == "-" || c == "." || c == "/"
}
func isWhitespace(c: Character?) -> Bool {
return isCharacterPartOfSet(c, set: NSCharacterSet.whitespaceCharacterSet())
}
public class Lexer {
var scanner: Scanner
var current: Token? = nil
var shouldStall = false
public init(scanner: Scanner) {
self.scanner = scanner
}
public func next() -> Token? {
if shouldStall {
shouldStall = false
return current
}
func work() -> Token {
if scanner.next() == nil { return Token(type: .EOF) }
scanner.stall()
while let info = scanner.next() where isWhitespace(info.character) {}
scanner.stall()
guard let next = scanner.next() else { return Token(type: .EOF) }
if next.character == "\n" {
return Token(type: .Terminal, value: "\n", line: next.line, column: next.column)
}
else if isValidIdentifierSignalCharacter(next.character) {
var content = String(next.character!)
while let info = scanner.next() where isValidIdenitifierCharacter(info.character) {
content.append(info.character!)
}
scanner.stall()
return Token(type: .Identifier, value: content, line: next.line, column: next.column)
}
else if next.character == "(" {
return Token(type: .OpenParen, value: "(", line: next.line, column: next.column)
}
else if next.character == ")" {
return Token(type: .CloseParen, value: ")", line: next.line, column: next.column)
}
else if next.character == "[" {
return Token(type: .OpenBracket, value: "[", line: next.line, column: next.column)
}
else if next.character == "]" {
return Token(type: .CloseBracket, value: "]", line: next.line, column: next.column)
}
else if next.character == "{" {
return Token(type: .OpenBrace, value: "{", line: next.line, column: next.column)
}
else if next.character == "}" {
return Token(type: .CloseBrace, value: "}", line: next.line, column: next.column)
}
else if next.character == ":" {
return Token(type: .Colon, value: ":", line: next.line, column: next.column)
}
else if next.character == ";" {
let column = scanner.peek()!.column
let line = scanner.peek()!.line
var comment = ""
while let info = scanner.next() where info.character == ";" {}
scanner.stall()
while let info = scanner.next() where info.character != "\n" {
comment.append(info.character!)
}
return Token(type: .Comment, value: comment, line: line, column: column)
}
else if next.character == "\"" {
var content = String(next.character!)
while let info = scanner.next() where info.character != "\"" {
content.append(info.character!)
}
content.append(scanner.peek()!.character!)
return Token(type: .StringLiteral, value: content, line: next.line, column: next.column)
}
else {
return Token(type: .Unknown, value: String(next.character!), line: next.line, column: next.column)
}
}
if self.current?.type == .EOF {
self.current = nil
}
else {
self.current = work()
}
return self.current
}
func tokenize() -> [Token] {
var tokens = [Token]()
while let token = self.next() { tokens.append(token) }
return tokens
}
public func peek() -> Token? {
return current
}
public func stall() {
shouldStall = true
}
}
\ No newline at end of file
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import atpkg
func outputBaseline(lexer: Lexer) {
print("--- baseline ---")
while let token = lexer.next() {
let type = String(reflecting: token.type).stringByReplacingOccurrencesOfString("atpkgparser.", withString: "")
var value = ""
switch token.type {
case .Terminal: value = "\\n"
case .StringLiteral: value = token.value.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
default: value = token.value
}
let output = "try test.assert(lexer.next() == Token(type: \(type), value: \"\(value)\", line: \(token.line), column: \(token.column)))"
print(output)
}
print("--- end baseline ---")
}
class LexerTests: Test {
required init() {}
let tests = [
LexerTests.testBasic
]
let filename = __FILE__
static func testBasic() throws {
let filepath = "./atpkg/tests/collateral/basic.atpkg"
let content: String = try NSString(contentsOfFile: filepath, encoding: NSUTF8StringEncoding) as String
let scanner = Scanner(content: content)
let lexer = Lexer(scanner: scanner)
try test.assert(lexer.next() == Token(type: TokenType.Comment, value: " This is the most basic of sample files.", line: 0, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 1, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.OpenParen, value: "(", line: 2, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "package", line: 2, column: 1))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 2, column: 8))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 3, column: 2))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "name", line: 3, column: 3))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"basic\"", line: 3, column: 8))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 3, column: 15))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 4, column: 2))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "version", line: 4, column: 3))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"0.1.0-dev\"", line: 4, column: 11))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 4, column: 22))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 5, column: 2))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 6, column: 2))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "tasks", line: 6, column: 3))
try test.assert(lexer.next() == Token(type: TokenType.OpenBrace, value: "{", line: 6, column: 9))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 6, column: 10))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "build", line: 6, column: 11))
try test.assert(lexer.next() == Token(type: TokenType.OpenBrace, value: "{", line: 6, column: 17))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 6, column: 18))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "tool", line: 6, column: 19))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"lldb-build\"", line: 6, column: 24))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 6, column: 36))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 7, column: 18))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "name", line: 7, column: 19))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"json-swift\"", line: 7, column: 24))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 7, column: 36))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 8, column: 18))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "output-type", line: 8, column: 19))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"lib\"", line: 8, column: 31))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 8, column: 37))
try test.assert(lexer.next() == Token(type: TokenType.Colon, value: ":", line: 9, column: 18))
try test.assert(lexer.next() == Token(type: TokenType.Identifier, value: "source", line: 9, column: 19))
try test.assert(lexer.next() == Token(type: TokenType.OpenBracket, value: "[", line: 9, column: 26))
try test.assert(lexer.next() == Token(type: TokenType.StringLiteral, value: "\"src/**.swift\"", line: 9, column: 28))
try test.assert(lexer.next() == Token(type: TokenType.CloseBracket, value: "]", line: 9, column: 43))
try test.assert(lexer.next() == Token(type: TokenType.CloseBrace, value: "}", line: 9, column: 44))
try test.assert(lexer.next() == Token(type: TokenType.CloseBrace, value: "}", line: 9, column: 45))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 9, column: 46))
try test.assert(lexer.next() == Token(type: TokenType.CloseParen, value: ")", line: 10, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 10, column: 1))
try test.assert(lexer.next() == Token(type: TokenType.Terminal, value: "\n", line: 11, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.Comment, value: " End of the sample.", line: 12, column: 0))
try test.assert(lexer.next() == Token(type: TokenType.EOF, value: "", line: 0, column: 0))
}
}
\ No newline at end of file
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import atpkg
extension ParseValue {
var stringLiteral: String? {
if case let .StringLiteral(value) = self { return value }
return nil
}
var map: [String:ParseValue]? {
if case let .Map(value) = self { return value }
return nil
}
var vector: [ParseValue]? {
if case let .Vector(value) = self { return value }
return nil
}
}
class ParserTests: Test {
required init() {}
let tests = [
ParserTests.testBasic
]
let filename = __FILE__
static func testBasic() throws {
let filepath = "./atpkg/tests/collateral/basic.atpkg"
guard let parser = Parser(filepath: filepath) else {
try test.assert(false); return
}
let result = try parser.parse()
let name = result.properties["name"]
try test.assert(name != nil)
try test.assert(name?.stringLiteral == "\"basic\"")
let version = result.properties["version"]
try test.assert(version != nil)
try test.assert(version?.stringLiteral == "\"0.1.0-dev\"")
let tasks = result.properties["tasks"]
try test.assert(tasks != nil)
let build = tasks?.map?["build"]
try test.assert(build != nil)
let tool = build?.map?["tool"]
try test.assert(tool != nil)
try test.assert(tool?.stringLiteral == "\"lldb-build\"")
let buildName = build?.map?["name"]
try test.assert(buildName != nil)
try test.assert(buildName?.stringLiteral == "\"json-swift\"")
let outputType = build?.map?["output-type"]
try test.assert(outputType != nil)
try test.assert(outputType?.stringLiteral == "\"lib\"")
let source = build?.map?["source"]
try test.assert(source != nil)
try test.assert(source?.vector != nil)
try test.assert(source?.vector?[0].stringLiteral == "\"src/**.swift\"")
}
}
\ No newline at end of file
This diff is collapsed.
;; This is the most basic of sample files.
(package
:name "basic"
:version "0.1.0-dev"
:tasks {:build {:tool "lldb-build"
:name "json-swift"
:output-type "lib"
:source [ "src/**.swift" ]}}
)
; End of the sample.
\ No newline at end of file
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// NOTE: This is the crappiest test thing ever... but it works for now.
extension String : ErrorType {}
enum test {
static func assert(condition: Bool, file: String = __FILE__, functionName: String = __FUNCTION__, line: Int = __LINE__) throws {
if !condition {
print(" \(file):\(line) \(functionName) **FAILED**")
throw "atpkg.tests.failed"
}
}
}
protocol Test {
init()
func runTests()
var tests: [() throws -> ()] { get }
var filename: String { get }
}
extension Test {
func runTests() {
print("Tests for \(filename)")
for test in tests {
do {
try test()
}
catch {
print("\(filename): **FAILED** \(error)")
}
}
}
}
print()
let tests: [Test] = [
// NOTE: Add your test classes here...
ScannerTests(),
LexerTests(),
ParserTests()
]
for test in tests {
test.runTests()
}
print()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment