1 module djinn.types;
2 
3 @safe:
4 
5 import std.array;
6 import std.algorithm;
7 import std.exception;
8 import std.range;
9 import std.utf;
10 
11 /// Avoids autodecoding
12 package alias string8b = typeof("".byCodeUnit);
13 
14 /// Tracks position in source code
15 struct Pos
16 {
17 	const(Source)* src;
18 	size_t offset;
19 
20 	size_t getLineNum() const pure
21 	{
22 		return src.getLineNum(offset);
23 	}
24 }
25 
26 /// A source code string with information about it
27 struct Source
28 {
29 	this(string fname, string orig) pure
30 	{
31 		import std..string : lineSplitter;
32 		this.fname = fname;
33 		this.orig = rem = orig.byCodeUnit;
34 		// The byte offsets of the start/end of each line are used for calculating line numbers
35 		line_offsets = chain(only(0), lineSplitter!(Yes.keepTerminator)(orig).map!(l => l.length).cumulativeFold!"a+b").array;
36 	}
37 
38 	bool empty() const pure
39 	{
40 		return rem.empty;
41 	}
42 
43 	size_t length() const pure
44 	{
45 		return rem.length;
46 	}
47 
48 	// Returns current 0-based byte offset into source
49 	size_t offset() const pure
50 	{
51 		return orig.length - rem.length;
52 	}
53 
54 	Pos curPos() return const pure
55 	{
56 		return posAt(offset);
57 	}
58 
59 	/// Converts 0-based byte offset to Pos
60 	Pos posAt(size_t o) return const pure
61 	{
62 		return Pos(&this, o);
63 	}
64 
65 	// Maps 0-based byte offset to 1-based line number
66 	size_t getLineNum(size_t o = offset()) const pure
67 	{
68 		// FIXME: support amortised O(line_offsets.length/tokens.length) approach
69 		assert (!line_offsets.empty);
70 		auto lines_after = assumeSorted(line_offsets).upperBound(o);
71 		return line_offsets.length - lines_after.length;
72 	}
73 
74 	const(string) fname;
75 	const(string8b) orig;
76 
77 	package:
78 	string8b rem;
79 	const(size_t[]) line_offsets;
80 }
81 
82 /// For Djinn syntax errors
83 class SyntaxException : Exception
84 {
85 	mixin basicExceptionCtors;
86 }
87 
88 package:
89 
90 enum TokenType
91 {
92 	text = 0,
93 	statements,
94 	expressions,
95 	directive,
96 }
97 
98 /// Jinja2 supports using - to indicate that whitespace should be stripped from the given side of the token
99 enum WhitespaceStripping
100 {
101 	none = 0,
102 	left = 1,
103 	right = 2,
104 	both = left | right,
105 }
106 
107 /// Pieces of Djinn source code
108 struct Token
109 {
110 	TokenType type;
111 	string value;
112 	Pos pos;
113 	WhitespaceStripping whitespace_stripping;
114 }
115 
116 alias TokenSink = Appender!(Token[]);
117 
118 SyntaxException syntaxException(string msg, const(Source)* src, string file = __FILE__, size_t line = __LINE__) pure
119 {
120 	const pos = src.curPos();
121 	return syntaxException(msg, pos, file, line);
122 }
123 
124 SyntaxException syntaxException(string msg, ref const(Pos) pos, string file = __FILE__, size_t line = __LINE__) pure
125 {
126 	import std.format : format;
127 	return new SyntaxException(format("%s(%d): %s", pos.src.fname, pos.getLineNum(), msg), file, line);
128 }