// // TikzParser.m // TikZiT // // Copyright 2010 Aleks Kissinger. All rights reserved. // // // This file is part of TikZiT. // // TikZiT is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // TikZiT 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with TikZiT. If not, see . // #import "TikzParser.h" // custom parsekit extensions #import "PKNaturalNumber.h" #import "PKRepetition+RepeatPlus.h" #import "PKSpecificDelimitedString.h" #import "PKBalancingDelimitState.h" #import "util.h" @interface TikzParser () - (NSString*)popAllToString:(PKAssembly*)a withSeparator:(NSString*)sep; - (PKParser*)eatSymbol:(NSString*)s; - (PKParser*)eatLiteral:(NSString*)s; - (PKParser*)eatWord; // properties - (void)willMatchProplist:(PKAssembly*)a; - (void)didMatchProplist:(PKAssembly*)a; - (void)willMatchProperty:(PKAssembly*)a; - (void)didMatchArrowSpecDash:(PKAssembly*)a; // nodes - (void)didMatchNodeCommand:(PKAssembly*)a; - (void)didMatchNodeLabel:(PKAssembly*)a; - (void)didMatchNode:(PKAssembly*)a; - (void)didMatchNodeName:(PKAssembly*)a; - (void)didMatchCoords:(PKAssembly*)a; // edges - (void)didMatchDrawCommand:(PKAssembly *)a; - (void)didMatchEdge:(PKAssembly*)a; - (void)didMatchEdgeNodeCommand:(PKAssembly*)a; - (void)didMatchEdgeNode:(PKAssembly*)a; // bounding box - (void)didMatchPathCommand:(PKAssembly*)a; - (void)didMatchBoundingBox:(PKAssembly*)a; // tikzpicture - (void)willMatchTikzPicture:(PKAssembly*)a; - (void)didMatchTikzPicture:(PKAssembly*)a; @end @implementation TikzParser @synthesize graph; - (id)init { [super init]; currentKey = nil; currentSourceArrow = nil; tokenizer = [PKTokenizer tokenizer]; graph = nil; currentNode = nil; currentEdge = nil; matchingEdgeNode = NO; // tweak the tokenizer a bit [tokenizer.symbolState remove:@"<="]; [tokenizer.symbolState remove:@">="]; [tokenizer.symbolState remove:@"{"]; [tokenizer.symbolState remove:@"}"]; [tokenizer setTokenizerState:tokenizer.wordState from:'\\' to:'\\']; [tokenizer.wordState setWordChars:NO from:'-' to:'-']; tokenizer.delimitState = [[PKBalancingDelimitState alloc] init]; [tokenizer.delimitState addStartMarker:@"{" endMarker:@"}" allowedCharacterSet:nil]; [tokenizer setTokenizerState:tokenizer.delimitState from:'{' to:'{']; /* GRAMMAR FOR TIKZPICTURE tikzpicture = '\begin' '{tikzpicture}' optproplist expr* '\end' '{tikzpicture}' expr = node | edge | boundingbox | layerexpr layerexpr = '\begin' '{pgfonlayer}' DelimitedString | '\end' '{pgfonlayer}' GRAMMAR FOR PROPERTY LISTS optproplist = proplist | Empty; proplist = '[' property property1* ']'; property = arrowspec | keyval | atom; property1 = ',' property; keyval = key '=' val; atom = propsym+; arrowspec = propsym* '-' propsym*; key = propsym+; val = propsym+ | QuotedString; propsym = (Word | Number | '<' | '>'); GRAMMAR FOR NODES node = '\node' optproplist name 'at' coords DelimitedString ';'; nodename = '(' nodeid ')'; nodeid = Word | NaturalNumber; coords = '(' Number ',' Number ')'; GRAMMAR FOR EDGES edge = '\draw' optproplist nodename 'to' optedgenode ( nodename | selfloop ) ';'; selfloop = '(' ')'; optedgenode = Empty | edgenode edgenode = 'node' optproplist name coords '{' '}' ';'; GRAMMAR FOR BOUNDING BOX boundingbox = '\path' '[' 'use' 'as' 'bounding' 'box' ']' coords 'rectangle' coords ';' */ PKAlternation *nodeid = [PKAlternation alternation]; nodeid.name = @"node identifier"; [nodeid add:[PKWord word]]; [nodeid add:[PKNaturalNumber number]]; PKAlternation *propsym = [PKAlternation alternation]; propsym.name = @"property symbol"; [propsym add:[PKWord word]]; [propsym add:[PKNumber number]]; [propsym add:[PKSymbol symbolWithString:@"<"]]; [propsym add:[PKSymbol symbolWithString:@">"]]; PKSequence *anchor = [PKSequence sequence]; [anchor add:[self eatSymbol:@"."]]; [anchor add:[self eatWord]]; PKAlternation *optanchor = [PKAlternation alternation]; [optanchor add:anchor]; [optanchor add:[PKEmpty empty]]; PKSequence *nodename = [PKSequence sequence]; nodename.name = @"node name"; [nodename add:[self eatSymbol:@"("]]; [nodename add:nodeid]; [nodename add:optanchor]; [nodename add:[self eatSymbol:@")"]]; [nodename setAssembler:self selector:@selector(didMatchNodeName:)]; PKTrack *coords = [PKTrack track]; coords.name = @"coordinate definition"; [coords add:[self eatSymbol:@"("]]; [coords add:[PKNumber number]]; [coords add:[self eatSymbol:@","]]; [coords add:[PKNumber number]]; [coords add:[self eatSymbol:@")"]]; [coords setAssembler:self selector:@selector(didMatchCoords:)]; PKSequence *key = [PKRepetition repetitionPlusWithSubparser:propsym]; PKAlternation *val = [PKAlternation alternation]; [val add:[PKRepetition repetitionPlusWithSubparser:propsym]]; [val add:[PKQuotedString quotedString]]; [val setPreassembler:self selector:@selector(willMatchVal:)]; PKSequence *keyval = [PKSequence sequence]; [keyval add:key]; [keyval add:[self eatLiteral:@"="]]; [keyval add:val]; PKSequence *atom = [PKRepetition repetitionPlusWithSubparser:propsym]; PKSymbol *arrowspecdash = [PKSymbol symbolWithString:@"-"]; [arrowspecdash setAssembler:self selector:@selector(didMatchArrowSpecDash:)]; PKSequence *arrowspec = [PKSequence sequence]; [arrowspec add:[PKRepetition repetitionWithSubparser:propsym]]; [arrowspec add:arrowspecdash]; [arrowspec add:[PKRepetition repetitionWithSubparser:propsym]]; PKAlternation *property = [PKAlternation alternation]; property.name = @"property, atom, or arrow specification"; [property add:keyval]; [property add:arrowspec]; [property add:atom]; [property setPreassembler:self selector:@selector(willMatchProperty:)]; PKSequence *property1 = [PKSequence sequence]; [property1 add:[self eatLiteral:@","]]; [property1 add:property]; PKTrack *proplist = [PKTrack track]; proplist.name = @"property list"; [proplist add:[self eatSymbol:@"["]]; [proplist add:property]; [proplist add:[PKRepetition repetitionWithSubparser:property1]]; [proplist add:[self eatSymbol:@"]"]]; [proplist setPreassembler:self selector:@selector(willMatchProplist:)]; [proplist setAssembler:self selector:@selector(didMatchProplist:)]; PKAlternation *optproplist = [PKAlternation alternation]; [optproplist add:proplist]; [optproplist add:[PKEmpty empty]]; PKLiteral *nodeCommand = [PKLiteral literalWithString:@"\\node"]; [nodeCommand setAssembler:self selector:@selector(didMatchNodeCommand:)]; PKTerminal *nodeLabel = [PKDelimitedString delimitedString]; nodeLabel.name = @"Possibly empty node label"; [nodeLabel setAssembler:self selector:@selector(didMatchNodeLabel:)]; PKTrack *node = [PKTrack track]; [node add:nodeCommand]; [node add:optproplist]; [node add:nodename]; [node add:[self eatLiteral:@"at"]]; [node add:coords]; [node add:nodeLabel]; //[node add:[[PKDelimitedString delimitedString] discard]]; [node add:[self eatSymbol:@";"]]; [node setAssembler:self selector:@selector(didMatchNode:)]; PKLiteral *drawCommand = [PKLiteral literalWithString:@"\\draw"]; [drawCommand setAssembler:self selector:@selector(didMatchDrawCommand:)]; PKSequence *parens = [PKSequence sequence]; [parens add:[self eatSymbol:@"("]]; [parens add:[self eatSymbol:@")"]]; PKAlternation *nodenamealt = [PKAlternation alternation]; nodenamealt.name = @"node name or '()'"; [nodenamealt add:nodename]; [nodenamealt add:parens]; PKLiteral *edgenodeCommand = [PKLiteral literalWithString:@"node"]; edgenodeCommand.name = @"edge node command"; [edgenodeCommand setAssembler:self selector:@selector(didMatchEdgeNodeCommand:)]; PKSequence *edgenode = [PKSequence sequence]; [edgenode add:edgenodeCommand]; [edgenode add:optproplist]; [edgenode add:nodeLabel]; edgenode.name = @"edge node"; [edgenode setAssembler:self selector:@selector(didMatchEdgeNode:)]; PKAlternation *optedgenode = [PKAlternation alternation]; [optedgenode add:[PKEmpty empty]]; [optedgenode add:edgenode]; PKTrack *edge = [PKTrack track]; [edge add:drawCommand]; [edge add:optproplist]; [edge add:nodename]; [edge add:[self eatLiteral:@"to"]]; [edge add:optedgenode]; [edge add:nodenamealt]; [edge add:[self eatSymbol:@";"]]; [edge setAssembler:self selector:@selector(didMatchEdge:)]; PKLiteral *pathliteral = [PKLiteral literalWithString:@"\\path"]; [pathliteral setAssembler:self selector:@selector(didMatchPathCommand:)]; PKTrack *boundingbox = [PKTrack track]; [boundingbox add:pathliteral]; [boundingbox add:[self eatSymbol:@"["]]; [boundingbox add:[self eatLiteral:@"use"]]; [boundingbox add:[self eatLiteral:@"as"]]; [boundingbox add:[self eatLiteral:@"bounding"]]; [boundingbox add:[self eatLiteral:@"box"]]; [boundingbox add:[self eatSymbol:@"]"]]; [boundingbox add:coords]; [boundingbox add:[self eatLiteral:@"rectangle"]]; [boundingbox add:coords]; [boundingbox add:[self eatSymbol:@";"]]; [boundingbox setAssembler:self selector:@selector(didMatchBoundingBox:)]; PKTerminal *layerLiteral = [[PKSpecificDelimitedString delimitedStringWithValue:@"{pgfonlayer}"] discard]; PKSequence *beginLayer = [PKSequence sequence]; [beginLayer add:[self eatLiteral:@"\\begin"]]; [beginLayer add:layerLiteral]; [beginLayer add:[[PKDelimitedString delimitedString] discard]]; PKSequence *endLayer = [PKSequence sequence]; [endLayer add:[self eatLiteral:@"\\end"]]; [endLayer add:layerLiteral]; PKAlternation *expr = [PKAlternation alternation]; [expr add:node]; [expr add:edge]; [expr add:boundingbox]; [expr add:beginLayer]; [expr add:endLayer]; PKTerminal *tikzpicLiteral = [[PKSpecificDelimitedString delimitedStringWithValue:@"{tikzpicture}"] discard]; //tikzpicLiteral.name = @"{tikzpicture}"; PKTrack *tikzpic = [PKTrack track]; [tikzpic add:[PKEmpty empty]]; [tikzpic add:[self eatLiteral:@"\\begin"]]; [tikzpic add:tikzpicLiteral]; [tikzpic add:optproplist]; [tikzpic add:[PKRepetition repetitionWithSubparser:expr]]; [tikzpic add:[self eatLiteral:@"\\end"]]; [tikzpic add:tikzpicLiteral]; [tikzpic setPreassembler:self selector:@selector(willMatchTikzPicture:)]; [tikzpic setAssembler:self selector:@selector(didMatchTikzPicture:)]; nodeParser = node; edgeParser = edge; tikzPictureParser = tikzpic; return self; } - (NSString*)popAllToString:(PKAssembly*)a withSeparator:(NSString*)sep { NSString *str = @""; BOOL fst = YES; while (![a isStackEmpty]) { if (fst) fst = NO; else str = [sep stringByAppendingString:str]; PKToken *tok = [a pop]; str = [tok.stringValue stringByAppendingString:str]; } return str; } - (PKParser*)eatSymbol:(NSString*)s { return [[PKSymbol symbolWithString:s] discard]; } - (PKParser*)eatLiteral:(NSString*)s { return [[PKLiteral literalWithString:s] discard]; } - (PKParser*)eatWord { return [[PKWord word] discard]; } - (void)packProperty:(PKAssembly*)a { BOOL empty = [a isStackEmpty]; NSString *val = [self popAllToString:a withSeparator:@" "]; if (currentKey != nil) { [elementData setProperty:val forKey:currentKey]; // NSLog(@" keyval: (%@) => (%@)", currentKey, val); } else if (currentSourceArrow != nil) { [elementData setArrowSpecFrom:currentSourceArrow to:val]; // NSLog(@" arrowspec: (%@-%@)", currentSourceArrow, val); } else if (!empty) { [elementData setAtom:val]; // NSLog(@" atom: (%@)", val); } currentKey = nil; currentSourceArrow = nil; } - (BOOL)parseNode:(NSString *)str { tokenizer.string = str; PKAssembly *res = [nodeParser completeMatchFor:[PKTokenAssembly assemblyWithTokenizer:tokenizer]]; // NSLog(@"result: %@", res); return res != nil; } - (BOOL)parseEdge:(NSString *)str { tokenizer.string = str; PKAssembly *res = [edgeParser completeMatchFor:[PKTokenAssembly assemblyWithTokenizer:tokenizer]]; // NSLog(@"result: %@", res); return res != nil; } - (BOOL)parseTikzPicture:(NSString*)str forGraph:(Graph*)g { self.graph = g; tokenizer.string = str; PKTokenAssembly *assm = [PKTokenAssembly assemblyWithTokenizer:tokenizer]; PKAssembly *res = [tikzPictureParser completeMatchFor:assm]; // NSLog(@"result: %@", res); return res != nil; } - (BOOL)parseTikzPicture:(NSString *)str { return [self parseTikzPicture:str forGraph:[Graph graph]]; } - (void)didMatchNodeCommand:(PKAssembly*)a { [a pop]; currentNode = [Node node]; [currentNode updateData]; // NSLog(@""); } - (void)didMatchNodeLabel:(PKAssembly*)a { PKToken *tok = [a pop]; NSString *s = tok.stringValue; s = [s substringWithRange:NSMakeRange(1, [s length]-2)]; if (matchingEdgeNode) currentEdge.edgeNode.label = s; else currentNode.label = s; } - (void)didMatchNode:(PKAssembly*)a { [nodeTable setObject:currentNode forKey:currentNode.name]; [graph addNode:currentNode]; currentNode = nil; // NSLog(@""); } - (void)didMatchDrawCommand:(PKAssembly*)a { [a pop]; currentEdge = [Edge edge]; sourceName = nil; targName = nil; // NSLog(@""); } - (void)didMatchEdge:(PKAssembly*)a { Node *src = [nodeTable objectForKey:sourceName]; currentEdge.source = src; currentEdge.target = (targName == nil) ? src : [nodeTable objectForKey:targName]; [currentEdge setAttributesFromData]; [graph addEdge:currentEdge]; currentEdge = nil; // NSLog(@""); } - (void)didMatchEdgeNodeCommand:(PKAssembly*)a { [a pop]; matchingEdgeNode = YES; currentEdge.edgeNode = [Node node]; } - (void)didMatchEdgeNode:(PKAssembly*)a { matchingEdgeNode = NO; } - (void)willMatchVal:(PKAssembly*)a { currentKey = [self popAllToString:a withSeparator:@" "]; // NSLog(@"key: %@", currentKey); } - (void)willMatchProperty:(PKAssembly*)a { [self packProperty:a]; } - (void)willMatchProplist:(PKAssembly*)a { elementData = [[GraphElementData alloc] init]; } - (void)didMatchProplist:(PKAssembly*)a { [self packProperty:a]; if (currentNode != nil) { currentNode.data = elementData; } else if (currentEdge != nil) { if (matchingEdgeNode) currentEdge.edgeNode.data = elementData; else currentEdge.data = elementData; } else { // add properties to to graph graph.data = elementData; } elementData = nil; } - (void)didMatchNodeName:(PKAssembly*)a { NSString *name = ((PKToken*)[a pop]).stringValue; if (currentNode != nil) { currentNode.name = name; // NSLog(@" name: (%@)", name); } else if (currentEdge != nil) { if (sourceName == nil) { sourceName = name; // NSLog(@" source: (%@)", name); } else { targName = name; // NSLog(@" target: (%@)", name); } } } - (void)didMatchCoords:(PKAssembly*)a { NSPoint p; p.y = ((PKToken*)[a pop]).floatValue; p.x = ((PKToken*)[a pop]).floatValue; if (currentNode != nil) { currentNode.point = p; } else { if (bboxFirstPoint) { bboxFirstPoint = NO; bbox1 = p; } else { bbox2 = p; } } // NSLog(@" coord: (%f, %f)", p.x, p.y); } - (void)didMatchPathCommand:(PKAssembly*)a { [a pop]; bboxFirstPoint = YES; } - (void)didMatchBoundingBox:(PKAssembly*)a { graph.boundingBox = NSRectAroundPoints(bbox1, bbox2); } - (void)didMatchArrowSpecDash:(PKAssembly*)a { [a pop]; // pop off the dash currentSourceArrow = [self popAllToString:a withSeparator:@" "]; } - (void)willMatchTikzPicture:(PKAssembly*)a { // NSLog(@""); nodeTable = [NSMutableDictionary dictionary]; } - (void)didMatchTikzPicture:(PKAssembly*)a { // NSLog(@""); // NSLog(@"%@", [graph tikz]); } - (void)finalize { // NSLog(@"releasing subparser trees"); PKReleaseSubparserTree(nodeParser); PKReleaseSubparserTree(edgeParser); PKReleaseSubparserTree(tikzPictureParser); [super finalize]; } @end