Last active
January 11, 2016 03:08
-
-
Save Stiivi/7745f18b382628bb9db8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Playground - noun: a place where people can play | |
// Note: we are using underscode instead of camelCase for gramar rules | |
// to maintain consistency with common practice in BNF | |
import ParserCombinator | |
extension Token: EmptyCheckable { | |
public static let EmptyValue = Token(.Empty, "") | |
public var isEmpty: Bool { return self.kind == TokenKind.Empty } | |
} | |
enum SymbolType: CustomStringConvertible { | |
case Tag | |
case Slot | |
case Counter | |
case Concept | |
var description: String { | |
switch self { | |
case .Tag: return "tag" | |
case .Slot: return "slot" | |
case .Counter: return "counter" | |
case .Concept: return "concept" | |
} | |
} | |
} | |
/// Partial AST – wraps only nodes that might not have immediate object | |
/// representation. For example elements of mixed-object lists. | |
// TODO: break this into smaller pieces, instead of this too generic wrapper | |
indirect enum AST { | |
case Tags(TagList) | |
case Slots([Symbol]) | |
case Counter(Symbol, Int) | |
case Modifier(String, AST) | |
case Selector(String, [AST]) | |
case Predicate(String, AST) | |
case ASTConcept(Concept) | |
case ASTActuator(Actuator) | |
} | |
// Terminals | |
// ======================================================================= | |
func token(kind: TokenKind, _ expected: String) -> Parser<Token, Token> { | |
return satisfy(expected) { token in token == kind } | |
} | |
func tokenValue(kind: TokenKind, _ value: String) -> Parser<Token, Token> { | |
return satisfy(value) { token in token == Token(kind, value) } | |
} | |
let symbol = { name in token(.Identifier, name) => { t in Symbol(t.text) } } | |
let number = { label in token(.IntLiteral, label) => { t in Int(t.text)! } } | |
let keyword = { kw in tokenValue(.Keyword, kw) => { t in t.text } } | |
let op = { o in tokenValue(.Operator, o) } | |
// TODO: This is just to type-hint the following rule. Otherwise Swift does not | |
// know that the `type` is of `SymbolType` | |
func typeDesc(type: SymbolType) -> String { | |
return type.description | |
} | |
// Primitive Items | |
// ======================================================================= | |
let symbol_list = { type in separated(symbol(typeDesc(type)), op(",")) } | |
let tag_list = symbol_list(.Tag) => { ast in TagList(ast) } | |
// String comparable | |
public prefix func §(value: String) -> Parser<Token, String>{ | |
return keyword(value) | |
} | |
public prefix func %(value: String) -> Parser<Token, Symbol>{ | |
return symbol(value) | |
} | |
infix operator ... { associativity left precedence 130 } | |
public func ...<T, A, B>(p: Parser<T,A>, sep:Parser<T,B>) -> Parser<T,[A]> { | |
return separated(p, sep) | |
} | |
// TODO: There must be a nicer way... | |
func makeConcept(name: String, _ members: [AST]) -> Concept { | |
let allTags: [TagList] = members.flatMap { | |
switch $0 { | |
case .Tags(let syms): return syms | |
default: return nil | |
} | |
} | |
let allSlots: [[Symbol]] = members.flatMap { | |
switch $0 { | |
case .Slots(let syms): return syms | |
default: return nil | |
} | |
} | |
let allCounters: [(String, Int)] = members.flatMap { | |
switch $0 { | |
case .Counter(let sym, let count): return (sym, count) | |
default: return nil | |
} | |
} | |
let tags = TagList(allTags.flatten()) | |
let slots = [Symbol](allSlots.flatten()) | |
let counters = CounterDict(items: allCounters) | |
return Concept(name: name, tags: tags, slots: slots, counters: counters) | |
} | |
// Concept | |
// ======================================================================= | |
let concept_member = | |
§"TAG" *> tag_list => { tags in AST.Tags(tags) } | |
|| §"SLOT" *> symbol_list(.Slot) => { syms in AST.Slots(syms) } | |
|| §"COUNTER" *> %"counter" + ((§"=" *> number("initial count")) || succeed(0)) | |
=> { (sym, count) in return AST.Counter(sym, count)} | |
let concept = | |
§"CONCEPT" *> %"name" + many(concept_member) => { (name, members) in makeConcept(name, members) } | |
let predicate_type = | |
§"SET" *> tag_list => { tags in PredicateType.TagSet(tags) } | |
|| §"BOUND" *> %"slot" => { symbol in PredicateType.CounterZero(symbol) } | |
|| §"ZERO" *> %"counter" => { symbol in PredicateType.IsBound(symbol)} | |
// Actuator | |
// ======================================================================== | |
// Selector | |
// ------------------------------------------------------------------------ | |
// §"NOT" *> succeed(true) || succeed(false) | |
let predicate = | |
(optionFlag(§"NOT") + option(§"IN" *> %"slot") + predicate_type) | |
=> { (ctx, type) in return Predicate(type, ctx.0, inSlot: ctx.1) } | |
let predicate_list = (predicate ... §"AND") | |
let selector = | |
§"ALL" => { _ in Selector.All } | |
|| §"ROOT" *> (predicate ... §"AND") => { Selector.Filter($0) } | |
|| (predicate ... §"AND") => { Selector.Root($0) } | |
// Modifier | |
// ------------------------------------------------------------------------ | |
let target_type = | |
§"THIS" *> succeed(TargetType.This) | |
|| §"OTHER" *> succeed(TargetType.Other) | |
|| §"ROOT" *> succeed(TargetType.Root) | |
|| succeed(TargetType.This) | |
let modifier_target = | |
target_type + option(%"slot") => { target in ModifierTarget(target.0, target.1) } | |
let bind_target = | |
modifier_target | |
|| %"slot" => { symbol in ModifierTarget(TargetType.This, symbol)} | |
let modifier_action = | |
§"NOTHING" => { _ in ModifierAction.Nothing} | |
|| §"SET" *> tag_list => { tags in ModifierAction.SetTags(tags)} | |
|| §"UNSET" *> tag_list => { tags in ModifierAction.UnsetTags(tags)} | |
|| §"INC" *> %"counter" => { symbol in ModifierAction.Inc(symbol)} | |
|| §"DEC" *> %"counter" => { symbol in ModifierAction.Dec(symbol)} | |
|| §"CLEAR" *> %"counter" => { symbol in ModifierAction.Clear(symbol)} | |
|| §"UNBIND" *> %"slot" => { symbol in ModifierAction.Unbind(symbol)} | |
|| §"BIND" *> %"slot" + (§"TO" *> bind_target) => { ast in ModifierAction.Bind(ast.1, ast.0)} | |
let modifier = | |
((§"IN" *> modifier_target) || succeed(ModifierTarget(.This) )) | |
+ modifier_action => { mod in Modifier(target: mod.0, action: mod.1) } | |
// WHERE something DO something | |
// WHERE something ON somethin else DO something | |
// ((selector, selector?), [modifier]) | |
let actuator = | |
((§"WHERE" *> selector) + option(§"ON" *> selector)) + (§"DO" *> some(modifier)) | |
=> { ast in Actuator(selector: ast.0.0, combinedSelector:ast.0.1, modifiers:ast.1) } | |
// AST | |
let model_object = | |
concept => { ast in AST.ASTConcept(ast) } | |
|| actuator => { ast in AST.ASTActuator(ast) } | |
let model = many(model_object) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment