1 |
|
2 | (function(){
|
3 | var identifierRegex, tokenRegex;
|
4 | identifierRegex = /[\$\w]+/;
|
5 | function peek(tokens){
|
6 | var token;
|
7 | token = tokens[0];
|
8 | if (token == null) {
|
9 | throw new Error('Unexpected end of input.');
|
10 | }
|
11 | return token;
|
12 | }
|
13 | function consumeIdent(tokens){
|
14 | var token;
|
15 | token = peek(tokens);
|
16 | if (!identifierRegex.test(token)) {
|
17 | throw new Error("Expected text, got '" + token + "' instead.");
|
18 | }
|
19 | return tokens.shift();
|
20 | }
|
21 | function consumeOp(tokens, op){
|
22 | var token;
|
23 | token = peek(tokens);
|
24 | if (token !== op) {
|
25 | throw new Error("Expected '" + op + "', got '" + token + "' instead.");
|
26 | }
|
27 | return tokens.shift();
|
28 | }
|
29 | function maybeConsumeOp(tokens, op){
|
30 | var token;
|
31 | token = tokens[0];
|
32 | if (token === op) {
|
33 | return tokens.shift();
|
34 | } else {
|
35 | return null;
|
36 | }
|
37 | }
|
38 | function consumeArray(tokens){
|
39 | var types;
|
40 | consumeOp(tokens, '[');
|
41 | if (peek(tokens) === ']') {
|
42 | throw new Error("Must specify type of Array - eg. [Type], got [] instead.");
|
43 | }
|
44 | types = consumeTypes(tokens);
|
45 | consumeOp(tokens, ']');
|
46 | return {
|
47 | structure: 'array',
|
48 | of: types
|
49 | };
|
50 | }
|
51 | function consumeTuple(tokens){
|
52 | var components;
|
53 | components = [];
|
54 | consumeOp(tokens, '(');
|
55 | if (peek(tokens) === ')') {
|
56 | throw new Error("Tuple must be of at least length 1 - eg. (Type), got () instead.");
|
57 | }
|
58 | for (;;) {
|
59 | components.push(consumeTypes(tokens));
|
60 | maybeConsumeOp(tokens, ',');
|
61 | if (')' === peek(tokens)) {
|
62 | break;
|
63 | }
|
64 | }
|
65 | consumeOp(tokens, ')');
|
66 | return {
|
67 | structure: 'tuple',
|
68 | of: components
|
69 | };
|
70 | }
|
71 | function consumeFields(tokens){
|
72 | var fields, subset, ref$, key, types;
|
73 | fields = {};
|
74 | consumeOp(tokens, '{');
|
75 | subset = false;
|
76 | for (;;) {
|
77 | if (maybeConsumeOp(tokens, '...')) {
|
78 | subset = true;
|
79 | break;
|
80 | }
|
81 | ref$ = consumeField(tokens), key = ref$[0], types = ref$[1];
|
82 | fields[key] = types;
|
83 | maybeConsumeOp(tokens, ',');
|
84 | if ('}' === peek(tokens)) {
|
85 | break;
|
86 | }
|
87 | }
|
88 | consumeOp(tokens, '}');
|
89 | return {
|
90 | structure: 'fields',
|
91 | of: fields,
|
92 | subset: subset
|
93 | };
|
94 | }
|
95 | function consumeField(tokens){
|
96 | var key, types;
|
97 | key = consumeIdent(tokens);
|
98 | consumeOp(tokens, ':');
|
99 | types = consumeTypes(tokens);
|
100 | return [key, types];
|
101 | }
|
102 | function maybeConsumeStructure(tokens){
|
103 | switch (tokens[0]) {
|
104 | case '[':
|
105 | return consumeArray(tokens);
|
106 | case '(':
|
107 | return consumeTuple(tokens);
|
108 | case '{':
|
109 | return consumeFields(tokens);
|
110 | }
|
111 | }
|
112 | function consumeType(tokens){
|
113 | var token, wildcard, type, structure;
|
114 | token = peek(tokens);
|
115 | wildcard = token === '*';
|
116 | if (wildcard || identifierRegex.test(token)) {
|
117 | type = wildcard
|
118 | ? consumeOp(tokens, '*')
|
119 | : consumeIdent(tokens);
|
120 | structure = maybeConsumeStructure(tokens);
|
121 | if (structure) {
|
122 | return structure.type = type, structure;
|
123 | } else {
|
124 | return {
|
125 | type: type
|
126 | };
|
127 | }
|
128 | } else {
|
129 | structure = maybeConsumeStructure(tokens);
|
130 | if (!structure) {
|
131 | throw new Error("Unexpected character: " + token);
|
132 | }
|
133 | return structure;
|
134 | }
|
135 | }
|
136 | function consumeTypes(tokens){
|
137 | var lookahead, types, typesSoFar, typeObj, type, structure;
|
138 | if ('::' === peek(tokens)) {
|
139 | throw new Error("No comment before comment separator '::' found.");
|
140 | }
|
141 | lookahead = tokens[1];
|
142 | if (lookahead != null && lookahead === '::') {
|
143 | tokens.shift();
|
144 | tokens.shift();
|
145 | }
|
146 | types = [];
|
147 | typesSoFar = {};
|
148 | if ('Maybe' === peek(tokens)) {
|
149 | tokens.shift();
|
150 | types = [
|
151 | {
|
152 | type: 'Undefined'
|
153 | }, {
|
154 | type: 'Null'
|
155 | }
|
156 | ];
|
157 | typesSoFar = {
|
158 | Undefined: true,
|
159 | Null: true
|
160 | };
|
161 | }
|
162 | for (;;) {
|
163 | typeObj = consumeType(tokens), type = typeObj.type, structure = typeObj.structure;
|
164 | if (!typesSoFar[type]) {
|
165 | types.push(typeObj);
|
166 | }
|
167 | if (structure == null) {
|
168 | typesSoFar[type] = true;
|
169 | }
|
170 | if (!maybeConsumeOp(tokens, '|')) {
|
171 | break;
|
172 | }
|
173 | }
|
174 | return types;
|
175 | }
|
176 | tokenRegex = RegExp('\\.\\.\\.|::|->|' + identifierRegex.source + '|\\S', 'g');
|
177 | module.exports = function(input){
|
178 | var tokens, e;
|
179 | if (!input.length) {
|
180 | throw new Error('No type specified.');
|
181 | }
|
182 | tokens = input.match(tokenRegex) || [];
|
183 | if (in$('->', tokens)) {
|
184 | throw new Error("Function types are not supported.\ To validate that something is a function, you may use 'Function'.");
|
185 | }
|
186 | try {
|
187 | return consumeTypes(tokens);
|
188 | } catch (e$) {
|
189 | e = e$;
|
190 | throw new Error(e.message + " - Remaining tokens: " + JSON.stringify(tokens) + " - Initial input: '" + input + "'");
|
191 | }
|
192 | };
|
193 | function in$(x, xs){
|
194 | var i = -1, l = xs.length >>> 0;
|
195 | while (++i < l) if (x === xs[i]) return true;
|
196 | return false;
|
197 | }
|
198 | }).call(this);
|