1 module context; 2 3 4 import std.string; 5 6 import tags; 7 import utils; 8 9 10 class ParserException : Throwable { 11 this(string msg, Context context) { 12 super(msg); 13 this.context = context; 14 } 15 16 Context context; 17 } 18 19 20 class Context { 21 this(string sourceName, string source, size_t line = 1, size_t cursor = 0) { 22 this.sourceName = sourceName; 23 this.source = source; 24 this.line = line; 25 this.cursor = cursor; 26 } 27 28 auto advance(size_t offset) { 29 line += source[cursor..cursor + offset].countLines; 30 cursor += offset; 31 return this; 32 } 33 34 auto remaining() { 35 return source[cursor..$]; 36 } 37 38 auto defining() { 39 return defineCount > 0; 40 } 41 42 auto tagOpen(string tag, string content) { 43 tagOpens ~= TagOpen(line, cursor, tag, content); 44 if (tag == Tag.Define) 45 ++defineCount; 46 } 47 48 auto tagClose() { 49 if (tagOpens.length == 0) 50 throw new ParserException("unexpected '/' tag; no tag is open", this); 51 auto tag = tagOpens[$-1]; 52 if (tag.tag == Tag.Define) 53 --defineCount; 54 tagOpens = tagOpens[0..$-1]; 55 return tag; 56 } 57 58 auto expectTagClosed() { 59 if (tagOpens.length) { 60 auto lastOpen = tagOpens[$-1]; 61 throw new ParserException("unexpected EOF; missing '/' tag for '" ~ lastOpen.tag ~ "'", new Context(sourceName, source, lastOpen.line, lastOpen.cursor)); 62 } 63 } 64 65 auto expectTagOpen(string tag, string related) { 66 if (!tagOpens.length || tagOpens[$-1].tag != tag) 67 throw new ParserException("unexpected '" ~ related ~ "' tag; no '" ~ tag ~ "' tag in open", this); 68 } 69 70 string sourceName; 71 string source; 72 size_t line; 73 size_t cursor; 74 size_t defineCount; 75 76 struct TagOpen { 77 size_t line; 78 size_t cursor; 79 string tag; 80 string content; 81 } 82 83 private TagOpen[] tagOpens; 84 }