Browse Source

Initial commit.

Stan Hebben 7 years ago
parent
commit
ac811913fc
60 changed files with 4137 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 8
    0
      codemodel/module.json
  3. 1
    0
      codemodel/src/AccessScope.zs
  4. 18
    0
      codemodel/src/CompareType.zs
  5. 223
    0
      codemodel/src/FunctionHeader.zs
  6. 57
    0
      codemodel/src/FunctionParameter.zs
  7. 31
    0
      codemodel/src/GenericName.zs
  8. 65
    0
      codemodel/src/HighLevelDefinition.zs
  9. 16
    0
      codemodel/src/WhitespaceInfo.zs
  10. 107
    0
      codemodel/src/ZSPackage.zs
  11. 20
    0
      codemodel/src/definition/AliasDefinition.zs
  12. 16
    0
      codemodel/src/definition/ClassDefinition.zs
  13. 17
    0
      codemodel/src/definition/DefinitionVisitor.zs
  14. 23
    0
      codemodel/src/definition/EnumDefinition.zs
  15. 19
    0
      codemodel/src/definition/ExpansionDefinition.zs
  16. 52
    0
      codemodel/src/definition/FunctionDefinition.zs
  17. 35
    0
      codemodel/src/expression/CallArguments.zs
  18. 67
    0
      codemodel/src/expression/Expression.zs
  19. 11
    0
      codemodel/src/generic/GenericParameterBound.zs
  20. 29
    0
      codemodel/src/generic/ParameterSuperBound.zs
  21. 29
    0
      codemodel/src/generic/ParameterTypeBound.zs
  22. 37
    0
      codemodel/src/generic/TypeParameter.zs
  23. 5
    0
      codemodel/src/generic/TypeParameterBoundVisitor.zs
  24. 7
    0
      codemodel/src/scope/TypeScope.zs
  25. 52
    0
      codemodel/src/type/ArrayTypeID.zs
  26. 52
    0
      codemodel/src/type/AssocTypeID.zs
  27. 47
    0
      codemodel/src/type/BasicTypeID.zs
  28. 48
    0
      codemodel/src/type/ConstTypeID.zs
  29. 146
    0
      codemodel/src/type/DefinitionTypeID.zs
  30. 53
    0
      codemodel/src/type/FunctionTypeID.zs
  31. 50
    0
      codemodel/src/type/GenericMapTypeID.zs
  32. 46
    0
      codemodel/src/type/GenericTypeID.zs
  33. 144
    0
      codemodel/src/type/GlobalTypeRegistry.zs
  34. 152
    0
      codemodel/src/type/ITypeID.zs
  35. 36
    0
      codemodel/src/type/ITypeVisitor.zs
  36. 40
    0
      codemodel/src/type/IteratorTypeID.zs
  37. 52
    0
      codemodel/src/type/OptionalTypeID.zs
  38. 50
    0
      codemodel/src/type/RangeTypeID.zs
  39. 30
    0
      codemodel/src/type/member/LocalMemberCache.zs
  40. 14
    0
      codemodel/src/type/member/TypeMember.zs
  41. 7
    0
      codemodel/src/type/member/TypeMemberPriority.zs
  42. 10
    0
      parser/module.json
  43. 111
    0
      parser/src/lexer/CharStream.zs
  44. 72
    0
      parser/src/lexer/CompiledDFA.zs
  45. 167
    0
      parser/src/lexer/DFA.zs
  46. 555
    0
      parser/src/lexer/NFA.zs
  47. 8
    0
      parser/src/lexer/Token.zs
  48. 314
    0
      parser/src/lexer/TokenStream.zs
  49. 5
    0
      parser/src/lexer/TokenType.zs
  50. 17
    0
      parser/src/lexer/ZSToken.zs
  51. 160
    0
      parser/src/lexer/ZSTokenStream.zs
  52. 164
    0
      parser/src/lexer/ZSTokenType.zs
  53. 19
    0
      project.json
  54. 4
    0
      shared/module.json
  55. 38
    0
      shared/src/CodePosition.zs
  56. 15
    0
      shared/src/CompileException.zs
  57. 50
    0
      shared/src/CompileExceptionCode.zs
  58. 7
    0
      shared/src/SourceFile.zs
  59. 496
    0
      shared/src/StringExpansion.zs
  60. 12
    0
      shared/src/Taggable.zs

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+outputJava/

+ 8
- 0
codemodel/module.json View File

@@ -0,0 +1,8 @@
1
+{
2
+	"package": "org.openzen.zencode.codemodel",
3
+	"host": "universal",
4
+	"dependencies": [
5
+		"collections",
6
+		"shared"
7
+	]
8
+}

+ 1
- 0
codemodel/src/AccessScope.zs View File

@@ -0,0 +1 @@
1
+export class AccessScope {}

+ 18
- 0
codemodel/src/CompareType.zs View File

@@ -0,0 +1,18 @@
1
+export enum CompareType {
2
+	LT("<"),
3
+	GT(">"),
4
+	EQ("=="),
5
+	NE("!="),
6
+	LE("<="),
7
+	GE(">="),
8
+	SAME("==="),
9
+	NOTSAME("!==");
10
+	
11
+	val str as string;
12
+	
13
+	this(str as string) {
14
+		this.str = str;
15
+	}
16
+
17
+	public as string => str;
18
+}

+ 223
- 0
codemodel/src/FunctionHeader.zs View File

@@ -0,0 +1,223 @@
1
+import .expression.CallArguments;
2
+import .expression.Expression;
3
+import .generic.TypeParameter;
4
+import .scope.TypeScope;
5
+import .type.BasicTypeID;
6
+import .type.GlobalTypeRegistry;
7
+import .type.ITypeID;
8
+import .type.member.LocalMemberCache;
9
+
10
+/**
11
+ *
12
+ * @author Hoofdgebruiker
13
+ */
14
+public class FunctionHeader {
15
+	static val NO_PARAMETERS as FunctionParameter[] = new FunctionParameter[](0);
16
+	static val NO_TYPE_PARAMETERS as TypeParameter[] = new TypeParameter[](0);
17
+	
18
+	val typeParameters as TypeParameter[] : get;
19
+	val returnType as ITypeID : get;
20
+	val parameters as FunctionParameter[] : get;
21
+	val thrownType as ITypeID : get;
22
+	
23
+	public this(returnType as ITypeID) {
24
+		this.typeParameters = NO_TYPE_PARAMETERS;
25
+		this.returnType = returnType;
26
+		this.parameters = NO_PARAMETERS;
27
+		this.thrownType = BasicTypeID.VOID;
28
+	}
29
+	
30
+	public this(returnType as ITypeID, parameters... as ITypeID) {
31
+		this.typeParameters = NO_TYPE_PARAMETERS;
32
+		this.returnType = returnType;
33
+		this.parameters = parameters.map(parameter => new FunctionParameter(parameter, null));
34
+		this.thrownType = BasicTypeID.VOID;
35
+	}
36
+	
37
+	public this(returnType as ITypeID, parameters... as FunctionParameter) {
38
+		this.typeParameters = NO_TYPE_PARAMETERS;
39
+		this.returnType = returnType;
40
+		this.parameters = parameters;
41
+		this.thrownType = BasicTypeID.VOID;
42
+	}
43
+	
44
+	public this(typeParameters as TypeParameter[], returnType as ITypeID, thrownType as ITypeID, parameters... as FunctionParameter) {
45
+		this.typeParameters = typeParameters;
46
+		this.returnType = returnType;
47
+		this.parameters = parameters;
48
+		this.thrownType = thrownType;
49
+	}
50
+
51
+	public get numberOfTypeParameters as int
52
+		=> typeParameters.length;
53
+	
54
+	public get doesThrow as bool
55
+		=> thrownType != BasicTypeID.VOID;
56
+	
57
+	public instance(registry as GlobalTypeRegistry, mapping as ITypeID[TypeParameter]) as FunctionHeader {
58
+		val genericParameters = this.typeParameters.map(parameter => parameter.withGenericArguments(registry, mapping));
59
+		val returnType = this.returnType.withGenericArguments(registry, mapping);
60
+		val parameters = this.parameters.map(parameter => parameter.withGenericArguments(registry, mapping));
61
+		val thrownType = this.thrownType.withGenericArguments(registry, mapping);
62
+		return new FunctionHeader(genericParameters, returnType, thrownType, parameters);
63
+	}
64
+	
65
+	public inferTypes(cache as LocalMemberCache, arguments as CallArguments, resultHint as ITypeID[]) as ITypeID[]? {
66
+		if arguments.arguments.length != this.parameters.length
67
+			return null;
68
+		
69
+		val mapping = new ITypeID[TypeParameter];
70
+		if !resultHint.empty {
71
+			val temp = new ITypeID[TypeParameter];
72
+			for hint in resultHint {
73
+				if returnType.inferTypeParameters(cache, hint, temp) {
74
+					mapping = temp;
75
+					break;
76
+				}
77
+			}
78
+		}
79
+		
80
+		// TODO: lambda header inference
81
+		for i, parameter in parameters
82
+			if !parameter.type.inferTypeParameters(cache, argument.arguments[i].type, mapping)
83
+				return null;
84
+		
85
+		if mapping.size > typeParameters.length
86
+			return null;
87
+		
88
+		val result = new ITypeID[](typeParameters.length);
89
+		for i, typeParameter in typeParameters {
90
+			if typeParameter !in mapping
91
+				return null;
92
+			
93
+			result[i] = mapping[typeParameter];
94
+		}
95
+		
96
+		return result;
97
+	}
98
+	
99
+	public hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
100
+		=> this.parameters
101
+			.contains((i, parameter) => parameter.type.hasInferenceBlockingTypeParameters(parameters));
102
+	
103
+	public canCastTo(scope as TypeScope, header as FunctionHeader) as bool {
104
+		if parameters.length != header.parameters.length
105
+			return false; // TODO: check type argument compatibility
106
+		
107
+		if !scope.getTypeMembers(returnType).canCastImplicit(header.returnType)
108
+			return false;
109
+		
110
+		return !parameters.contains((i, parameter)
111
+			=> !scope.getTypeMembers(header.parameters[i].type).canCastImplicit(parameter.type));
112
+	}
113
+	
114
+	public accepts(scope as TypeScope, arguments... as Expression) as bool {
115
+		if parameters.length != arguments.length
116
+			return false;
117
+		
118
+		return !arguments.contains((i, argument) => !scope
119
+			.getTypeMembers(argument.type)
120
+			.canCastImplicit(parameters[i].type));
121
+	}
122
+	
123
+	/**
124
+	 * Checks if two function headers are equivalent. Functions headers are
125
+	 * equivalent if their types are the same.
126
+	 * 
127
+	 * @param other
128
+	 * @return 
129
+	 */
130
+	public isEquivalentTo(other as FunctionHeader) as bool {
131
+		if parameters.length != other.parameters.length
132
+			return false;
133
+		
134
+		return !parameters.contains((i, parameter) => parameter.type !== other.parameters[i].type);
135
+	}
136
+	
137
+	/**
138
+	 * Checks if two function headers are similar. "similar" means that there
139
+	 * exists a set of parameters for which there is no way to determine which
140
+	 * one to call.
141
+	 * 
142
+	 * Note that this does not mean that there is never confusion about which
143
+	 * method to call. There can be confusion due to implicit conversions. This
144
+	 * can be resolved by performing the conversions explicitly.
145
+	 * 
146
+	 * It is illegal to have two similar methods with the same name.
147
+	 * 
148
+	 * @param other
149
+	 * @return 
150
+	 */
151
+	public isSimilarTo(other as FunctionHeader) as bool {
152
+		val common = int.min(parameters.length, other.parameters.length);
153
+		for i in 0 .. common
154
+			if parameters[i].type !== other.parameters[i].type
155
+				return false;
156
+		
157
+		for i in common .. parameters.length
158
+			if parameters[i].defaultValue == null
159
+				return false;
160
+		
161
+		for i in common .. other.parameters.length
162
+			if other.parameters[i].defaultValue == null
163
+				return false;
164
+		
165
+		return true;
166
+	}
167
+	
168
+	public withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as FunctionHeader {
169
+		val returnType = this.returnType.withGenericArguments(registry, arguments);
170
+		val parameters = this.parameters.map(parameter => {
171
+			val modified = parameter.type.withGenericArguments(registry, arguments);
172
+			return modified == parameter.type ? parameter : new FunctionParameter(modified, parameter.name);
173
+		});
174
+		return new FunctionHeader(
175
+			typeParameters,
176
+			returnType,
177
+			thrownType.withGenericArguments(registry, arguments),
178
+			parameters);
179
+	}
180
+	
181
+	public withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[]) as FunctionHeader {
182
+		val typeArguments = new ITypeID[TypeParameter];
183
+		for i, typeParameter in typeParameters
184
+			typeArguments[typeParameter] = arguments[i];
185
+		
186
+		return withGenericArguments(registry, typeArguments);
187
+	}
188
+	
189
+	public forTypeParameterInference() as FunctionHeader {
190
+		return new FunctionHeader(BasicTypeID.UNDETERMINED, parameters);
191
+	}
192
+	
193
+	public forLambda(lambdaHeader as FunctionHeader) as FunctionHeader {
194
+		val parameters = lambdaHeader.parameters
195
+			.map((i, parameter) => new FunctionParameter(this.parameters[i].type, parameter.name));
196
+		return new FunctionHeader(typeParameters, returnType, thrownType, parameters);
197
+	}
198
+	
199
+	public getVariadicParameter() as FunctionParameter?
200
+		=> !parameters.empty && parameters.last.variadic ? parameters.last : null;
201
+	
202
+	public explainWhyIncompatible(scope as TypeScope, arguments as CallArguments) as string {
203
+		if this.parameters.length != arguments.arguments.length
204
+			return parameters.length + " parameters expected but " + arguments.arguments.length + " given.";
205
+		
206
+		if numberOfTypeParameters != arguments.numberOfTypeArguments
207
+			return numberOfTypeParameters + " type parameters expected but " + arguments.numberOfTypeArguments + " given.";
208
+		
209
+		for i, parameter in parameters
210
+			if !scope.getTypeMembers(arguments.arguments[i].type).canCastImplicit(parameter.type)
211
+				return "Parameter " + i + ": cannot cast " + arguments.arguments[i].type + " to " + parameter.type;
212
+		
213
+		return "Method should be compatible";
214
+	}
215
+	
216
+	public as string {
217
+		val result = new StringBuilder();
218
+		if typeParameters.length > 0
219
+			result << '<' << typeParameters.map(param => param as string).join(', ') << '>';
220
+		result << '(' << parameters.map(parameter => parameter as string).join(', ') < ')';
221
+		return result as string;
222
+	}
223
+}

+ 57
- 0
codemodel/src/FunctionParameter.zs View File

@@ -0,0 +1,57 @@
1
+import .expression.Expression;
2
+import .generic.TypeParameter;
3
+import .type.GlobalTypeRegistry;
4
+import .type.ITypeID;
5
+import shared.Taggable;
6
+
7
+export class FunctionParameter : Taggable {
8
+	static val NONE as FunctionParameter[] : get = [];
9
+	
10
+	val type as ITypeID : get;
11
+	val name as string : get;
12
+	val defaultValue as Expression : get;
13
+	val variadic as bool : get;
14
+	
15
+	public this(type as ITypeID) {
16
+		this.type = type;
17
+		this.name = "";
18
+		this.defaultValue = null;
19
+		this.variadic = false;
20
+	}
21
+	
22
+	public this(type as ITypeID, name as string) {
23
+		this.type = type;
24
+		this.name = name;
25
+		this.defaultValue = null;
26
+		this.variadic = false;
27
+	}
28
+	
29
+	public this(type as ITypeID, name as string, defaultValue as Expression, variadic as bool) {
30
+		this.type = type;
31
+		this.name = name;
32
+		this.defaultValue = defaultValue;
33
+		this.variadic = variadic;
34
+	}
35
+	
36
+	public withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as FunctionParameter
37
+		=> new FunctionParameter(type.withGenericArguments(registry, arguments), name, defaultValue, variadic);
38
+	
39
+	public as string
40
+		=> name + " as " + type as string;
41
+
42
+	public implements Hashable<FunctionParameter> {
43
+		hashCode() as int {
44
+			var hash = 5;
45
+			hash = 17 * hash + this.type.hashCode();
46
+			hash = 17 * hash + (this.variadic ? 1 : 0);
47
+			return hash;
48
+		}
49
+
50
+		equals(other as FunctionParameter) {
51
+			if this === other
52
+				return true;
53
+			
54
+			return this.type == other.type && this.variadic == other.variadic;
55
+		}
56
+	}
57
+}

+ 31
- 0
codemodel/src/GenericName.zs View File

@@ -0,0 +1,31 @@
1
+import .type.ITypeID;
2
+
3
+export struct GenericName {
4
+	val name as string : get;
5
+	val arguments as ITypeID[] : get;
6
+	
7
+	public this(name as string) {
8
+		this(name, []);
9
+	}
10
+	
11
+	public this(name as string, arguments as ITypeID[]) {
12
+		this(name, arguments);
13
+	}
14
+	
15
+	public get numberOfArguments as int
16
+		=> arguments.length;
17
+	
18
+	public get hasArguments as bool
19
+		=> !arguments.empty;
20
+	
21
+	public get hasNoArguments as bool
22
+		=> arguments.empty;
23
+		
24
+	public as string {
25
+		val result = new StringBuilder();
26
+		result << name;
27
+		if hasArguments
28
+			result << '<' << arguments.map(argument => argument as string).join(', ') << '>';
29
+		return result as string;
30
+	}
31
+}

+ 65
- 0
codemodel/src/HighLevelDefinition.zs View File

@@ -0,0 +1,65 @@
1
+import .definition.DefinitionVisitor;
2
+import .generic.TypeParameter;
3
+import .member.ConstructorMember;
4
+import .member.EnumConstantMember;
5
+import .member.FieldMember;
6
+import .member.IDefinitionMember;
7
+import .type.ITypeID;
8
+import shared.CodePosition;
9
+import shared.Taggable;
10
+
11
+public abstract class HighLevelDefinition : Taggable {
12
+	val position as CodePosition : get;
13
+	val package as ZSPackage : get;
14
+	val name as string : get;
15
+	val modifiers as int : get;
16
+	val members as IDefinitionMember[] : get;
17
+	val typeParameters as TypeParameter[] : get = [];
18
+	
19
+	val outerDefinition as HighLevelDefinition? : get;
20
+	val superType as ITypeID? : get, set;
21
+	
22
+	public this(
23
+			position as CodePosition,
24
+			package as ZSPackage,
25
+			name as string,
26
+			modifiers as int,
27
+			outerDefinition as HighLevelDefinition?)
28
+	{
29
+		this.position = position;
30
+		this.pkg = pkg;
31
+		this.name = name;
32
+		this.modifiers = modifiers;
33
+		this.outerDefinition = outerDefinition;
34
+		
35
+		package.register(this);
36
+	}
37
+	
38
+	public get numerOfTypeParameters as int
39
+		=> genericParameters.length;
40
+	
41
+	public set outerDefinition as HighLevelDefinition
42
+		=> outerDefinition = $;
43
+	
44
+	public get isInnerDefinition
45
+		=> outerDefinition != null;
46
+	
47
+	public addMember(member as IDefinitionMember) as void {
48
+		if member !in members
49
+			members.add(member);
50
+	}
51
+	
52
+	public set typeParameters as TypeParameter[]
53
+		=> typeParameters = $;
54
+	
55
+	public get fields as FieldMember[]
56
+		=> members.filter(member => member is FieldMember).map(member as FieldMember);
57
+	
58
+	public get hasEmptyConstructor as bool 
59
+		=> members.contains(member
60
+			=> (member is ConstructorMember) && (member as ConstructorMember).header.parameters.empty);
61
+	
62
+	public get isStatic => (modifiers & Modifiers.STATIC) > 0;
63
+	
64
+	public abstract accept<T>(visitor as DefinitionVisitor<T>) as T;
65
+}

+ 16
- 0
codemodel/src/WhitespaceInfo.zs View File

@@ -0,0 +1,16 @@
1
+export struct WhitespaceInfo {
2
+	public static from(whitespaceBefore as string, lineAfter as string, skipLineBefore as bool) as WhitespaceInfo {
3
+		val numNewLines = whitespaceBefore.characters.count(c => c == '\n');
4
+		val commentsBefore = whitespaceBefore
5
+			.split('\n')
6
+			.map(value => value.trim())
7
+			.filter(value => !value.empty);
8
+		
9
+		val emptyLine = !skipLineBefore && numNewLines - commentsBefore.length > 0;
10
+		return { emptyLine, commentsBefore, lineAfter.trim() };
11
+	}
12
+	
13
+	val emptyLine as bool : get;
14
+	val commentsBefore as string[] : get;
15
+	val commentsAfter as string : get;
16
+}

+ 107
- 0
codemodel/src/ZSPackage.zs View File

@@ -0,0 +1,107 @@
1
+import shared.CompileException;
2
+import shared.CompileExceptionCode;
3
+import .HighLevelDefinition;
4
+import .partial.IPartialExpression;
5
+import .partial.PartialPackageExpression;
6
+import .partial.PartialTypeExpression;
7
+import .scope.TypeScope;
8
+import .type.GenericName;
9
+import .type.GlobalTypeRegistry;
10
+import .type.ITypeID;
11
+import shared.CodePosition;
12
+
13
+export class ZSPackage {
14
+	val fullName as string : get;
15
+	val subPackages as ZSPackage[string] = {};
16
+	val types as HighLevelDefinition[string] = {};
17
+	
18
+	public this(fullName as string) {
19
+		this.fullName = fullName;
20
+	}
21
+	
22
+	public add(name as string, package as ZSPackage) as void {
23
+		if name in subPackages
24
+			panic("Such package already exists: " + name);
25
+		
26
+		subPackages[name] = package;
27
+	}
28
+	
29
+	public getMember(position as CodePosition, registry as GlobalTypeRegistry, name as GenericName) as IPartialExpression {
30
+		if name.name in subPackages && name.hasNoArguments
31
+			return new PartialPackageExpression(position, subPackages[name.name]);
32
+		
33
+		if name.name in types {
34
+			if types[name.name].genericParameters.length != name.numberOfArguments
35
+				throw new CompileException(position, TYPE_ARGUMENTS_INVALID_NUMBER, "Invalid number of type arguments");
36
+			
37
+			return new PartialTypeExpression(position, registry.getForDefinition(types[name.name], name.arguments), name.arguments);
38
+		}
39
+		
40
+		return null;
41
+	}
42
+	
43
+	public in(name as string) as bool
44
+		=> name in types || name in subPackages;
45
+	
46
+	public getDefinition(name as string) as HighLevelDefinition?
47
+		=> types.getOrDefault(name);
48
+	
49
+	public getImport(name as string[], depth as int) as HighLevelDefinition? {
50
+		if depth >= name.length
51
+			return null;
52
+		
53
+		if name[depth] in subPackages
54
+			return subPackages[name[depth]].getImport(name, depth + 1);
55
+		
56
+		if depth == name.length - 1 && name[depth] in types
57
+			return types[name[depth]];
58
+		
59
+		return null;
60
+	}
61
+	
62
+	public getType(position as CodePosition, scope as TypeScope, nameParts as GenericName[]) as ITypeID?
63
+		=> getType(position, scope, nameParts, 0);
64
+	
65
+	public getType(position as CodePosition, scope as TypeScope, name as GenericName) as ITypeID? {
66
+		if name.name in types
67
+			return scope.getTypeRegistry().getForDefinition(types[name.name], name.arguments);
68
+		
69
+		return null;
70
+	}
71
+	
72
+	private getType(position as CodePosition, scope as TypeScope, nameParts as GenericName[], depth as int) as ITypeID? {
73
+		if depth >= nameParts.length
74
+			return null;
75
+		
76
+		val name = nameParts[depth];
77
+		if name.name in subPackages && name.hasNoArguments
78
+			return subPackages[name.name].getType(position, scope, nameParts, depth + 1);
79
+		
80
+		if name.name in types {
81
+			var type = scope.getTypeRegistry().getForDefinition(types[name.name], name.arguments);
82
+			depth++;
83
+			while depth < nameParts.length {
84
+				val innerName = nameParts[depth++];
85
+				type = scope.getTypeMembers(type).getInnerType(position, innerName);
86
+				if type == null
87
+					return null;
88
+			}
89
+			
90
+			return type;
91
+		}
92
+		
93
+		return null;
94
+	}
95
+	
96
+	public getOrCreatePackage(name as string) as ZSPackage {
97
+		if name in subPackages
98
+			return subPackages[name];
99
+		
100
+		val result = new ZSPackage(fullName.empty ? name : fullName + '.' + name);
101
+		subPackages[name] = result;
102
+		return result;
103
+	}
104
+	
105
+	public register(definition as HighLevelDefinition) as void
106
+		=> types[definition.name] = definition;
107
+}

+ 20
- 0
codemodel/src/definition/AliasDefinition.zs View File

@@ -0,0 +1,20 @@
1
+import .HighLevelDefinition;
2
+import .type.ITypeID;
3
+import shared.CodePosition;
4
+
5
+export class AliasDefinition : HighLevelDefinition {
6
+	var type as ITypeID : get, set;
7
+	
8
+	public this(
9
+		position as CodePosition,
10
+		packages as ZSPackage,
11
+		name as string,
12
+		modifiers as int,
13
+		outerDefinition as HighLevelDefinition)
14
+	{
15
+		super(position, pkg, name, modifiers, outerDefinition);
16
+	}
17
+	
18
+	override accept<T>(visitor as DefinitionVisitor<T>) as T
19
+		=> visitor.visitAlias(this);
20
+}

+ 16
- 0
codemodel/src/definition/ClassDefinition.zs View File

@@ -0,0 +1,16 @@
1
+import .HighLevelDefinition;
2
+import .type.ITypeID;
3
+import shared.CodePosition;
4
+
5
+export class ClassDefinition : HighLevelDefinition {
6
+	public this(position as CodePosition, package as ZSPackage, name as string, modifiers as int) {
7
+		this(position, pkg, name, modifiers, null);
8
+	}
9
+	
10
+	public this(position as CodePosition, package as ZSPackage, name as string, modifiers as int, outerDefinition as HighLevelDefinition) {
11
+		super(position, pkg, name, modifiers, outerDefinition);
12
+	}
13
+
14
+	override accept<T>(visitor as DefinitionVisitor<T>) as T
15
+		=> visitor.visitClass(this);
16
+}

+ 17
- 0
codemodel/src/definition/DefinitionVisitor.zs View File

@@ -0,0 +1,17 @@
1
+export interface DefinitionVisitor<T> {
2
+	visitClass(definition as ClassDefinition) as T;
3
+	
4
+	visitInterface(definition as InterfaceDefinition) as T;
5
+	
6
+	visitEnum(definition as EnumDefinition) as T;
7
+	
8
+	visitStruct(definition as StructDefinition) as T;
9
+	
10
+	visitFunction(definition as FunctionDefinition) as T;
11
+	
12
+	visitExpansion(definition as ExpansionDefinition) as T;
13
+	
14
+	visitAlias(definition as AliasDefinition) as T;
15
+	
16
+	visitVariant(definition as VariantDefinition) as T;
17
+}

+ 23
- 0
codemodel/src/definition/EnumDefinition.zs View File

@@ -0,0 +1,23 @@
1
+import .HighLevelDefinition;
2
+import .member.EnumConstantMember;
3
+import shared.CodePosition;
4
+
5
+export class EnumDefinition : HighLevelDefinition {
6
+	val enumConstants as EnumConstantMember[] : get = [];
7
+	
8
+	public this(
9
+		position as CodePosition,
10
+		package as ZSPackage,
11
+		name as string,
12
+		modifiers as int,
13
+		outerDefinition as HighLevelDefinition)
14
+	{
15
+		super(position, pkg, name, modifiers, outerDefinition);
16
+	}
17
+
18
+	override accept<T>(visitor as DefinitionVisitor<T>) as T
19
+		=> visitor.visitEnum(this);
20
+	
21
+	public addEnumConstant(constant as EnumConstantMember) as void
22
+		=> enumConstants.add(constant);
23
+}

+ 19
- 0
codemodel/src/definition/ExpansionDefinition.zs View File

@@ -0,0 +1,19 @@
1
+import .HighLevelDefinition;
2
+import .type.ITypeID;
3
+import shared.CodePosition;
4
+
5
+export class ExpansionDefinition : HighLevelDefinition {
6
+	val target as ITypeID : get;
7
+	
8
+	public this(
9
+		position as CodePosition,
10
+		package as ZSPackage,
11
+		modifiers as int,
12
+		outerDefinition as HighLevelDefinition)
13
+	{
14
+		super(position, pkg, null, modifiers, outerDefinition);
15
+	}
16
+	
17
+	override accept<T>(visitor as DefinitionVisitor<T>) as T
18
+		=> visitor.visitExpansion(this);
19
+}

+ 52
- 0
codemodel/src/definition/FunctionDefinition.zs View File

@@ -0,0 +1,52 @@
1
+import .FunctionHeader;
2
+import .HighLevelDefinition;
3
+import .Modifiers;
4
+import .member.CallerMember;
5
+import .statement.Statement;
6
+import .type.member.DefinitionMemberGroup;
7
+import .type.member.TypeMemberPriority;
8
+import shared.CodePosition;
9
+
10
+/**
11
+ *
12
+ * @author Hoofdgebruiker
13
+ */
14
+public class FunctionDefinition : HighLevelDefinition {
15
+	val _header as FunctionHeader;
16
+	val statement as Statement : get;
17
+	val caller as CallerMember : get;
18
+	val callerGroup as DefinitionMemberGroup : get;
19
+	
20
+	public this(
21
+		position as CodePosition,
22
+		package as ZSPackage,
23
+		name as string,
24
+		modifiers as int,
25
+		outerDefinition as HighLevelDefinition?)
26
+	{
27
+		super(position, pkg, name, modifiers, outerDefinition);
28
+		callerGroup = new DefinitionMemberGroup(true, name);
29
+	}
30
+	
31
+	public this(position as CodePosition, package as ZSPackage, name as string, modifiers as int, header as FunctionHeader) {
32
+		this(position, pkg, name, modifiers, null as HighLevelDefinition?);
33
+		setHeader(header);
34
+	}
35
+	
36
+	public get header as FunctionDefinition
37
+		=> _header;
38
+	
39
+	public set header as FunctionDefinition {
40
+		_header = $;
41
+		addMember(caller = new CallerMember(position, this, modifiers | Modifiers.STATIC, $));
42
+		callerGroup.addMethod(caller, SPECIFIED);
43
+	}
44
+	
45
+	public set code as Statement {
46
+		this.statement = $;
47
+		caller.setBody($);
48
+	}
49
+
50
+	override accept<T>(visitor) as T
51
+		=> visitor.visitFunction(this);
52
+}

+ 35
- 0
codemodel/src/expression/CallArguments.zs View File

@@ -0,0 +1,35 @@
1
+import .type.ITypeID;
2
+
3
+export class CallArguments {
4
+	static val NO_TYPE_ARGUMENTS as ITypeID[] : get = new ITypeID[](0);
5
+	static val EMPTY as CallArguments : get = new CallArguments(new Expression[](0));
6
+	
7
+	val typeArguments as ITypeID[] : get;
8
+	val arguments as Expression[] : get;
9
+	
10
+	public this(arguments... as Expression) {
11
+		this.typeArguments = NO_TYPE_ARGUMENTS;
12
+		this.arguments = arguments;
13
+	}
14
+	
15
+	public this(typeArguments as ITypeID[], arguments as Expression[]) {
16
+		this.typeArguments = typeArguments;
17
+		this.arguments = arguments;
18
+	}
19
+	
20
+	public get numberOfTypeArguments as int
21
+		=> typeArguments.length;
22
+	
23
+	public normalize(position as CodePosition, scope as TypeScope, header as FunctionHeader) as CallArguments {
24
+		var newArguments = arguments.map((i, argument) => argument.castImplicit(position, scope, header.parameters[i].type));
25
+		if (arguments.length < header.parameters.length) {
26
+			newArguments = newArguments.resize(arguments, i => {
27
+				if header.parameters[i].defaultValue == null
28
+					throw new CompileException(position, CompileExceptionCode.MISSING_PARAMETER, "Parameter missing and no default value specified");
29
+				
30
+				return header.parameters[i].defaultValue;
31
+			});
32
+		}
33
+		return new CallArguments(typeArguments, newArguments);
34
+	}
35
+}

+ 67
- 0
codemodel/src/expression/Expression.zs View File

@@ -0,0 +1,67 @@
1
+public abstract class Expression {
2
+	val position as CodePosition : get;
3
+	val type as ITypeID : get;
4
+	val thrownType as ITypeID : get;
5
+	
6
+	public this(position as CodePosition, type as ITypeID, thrownType as ITypeID) {
7
+		this.position = position;
8
+		this.type = type;
9
+		this.thrownType = thrownType;
10
+	}
11
+	
12
+	public abstract accept<T>(visitor as ExpressionVisitor<T>) as T;
13
+	
14
+	public castExplicit(position as CodePosition, scope as TypeScope, asType as ITypeID, optional as bool) as Expression
15
+		=> scope.getTypeMembers(type).castExplicit(position, this, asType, optional);
16
+	
17
+	public castImplicit(position as CodePosition, scope as TypeScope, asType as ITypeID) as Expression
18
+		=> scope.getTypeMembers(type).castImplicit(position, this, asType, true);
19
+	
20
+	public implements IPartialExpression {
21
+		get assignHints as ITypeID[]
22
+			=> [type];
23
+		
24
+		eval() as Expression
25
+			=> this;
26
+	
27
+		predictCallTypes(scope as TypeScope, hints as ITypeID[], arguments as int) as ITypeID[][]
28
+			=> scope.getTypeMembers(type).getOrCreateGroup(CALL).predictCallTypes(scope, hints, arguments);
29
+		
30
+		getPossibleFunctionHeaders(scope as TypeScope, hints as ITypeID[], arguments as int) as FunctionHeader[]
31
+			=> scope.getTypeMembers(type)
32
+					.getOrCreateGroup(CALL)
33
+					.methodMembers
34
+					.filter(method => method.member.header.parameters.length == arguments && !method.member.isStatic)
35
+					.map(method => method.member.header);
36
+		
37
+		call(position as CodePosition, scope as TypeScope, hints as ITypeID[], arguments as CallArguments) as Expression
38
+			=> scope.getTypeMembers(type).getOrCreateGroup(CALL).call(position, scope, this, arguments, false);
39
+		
40
+		getMember(position as CodePosition, scope as TypeScope, hints as ITypeID[], name as GenericName) as IPartialExpression
41
+			=> scope.getTypeMembers(type).getMemberExpression(position, this, name, false);
42
+		
43
+		getGenericCallTypes() as ITypeID[]?
44
+			=> null;
45
+	}
46
+	
47
+	public static binaryThrow(position as CodePosition, left as ITypeID?, right as ITypeID?) as ITypeID? {
48
+		if left == right
49
+			return left;
50
+		else if left == null
51
+			return right;
52
+		else if right == null
53
+			return left;
54
+		
55
+		throw new CompileException(
56
+			position,
57
+			DIFFERENT_EXCEPTIONS,
58
+			"two different exceptions in same operation: " + left as string + " and " + right as string);
59
+	}
60
+	
61
+	public static multiThrow(position as CodePosition, expressions as Expression[]) as ITypeID? {
62
+		val result as ITypeID? = null;
63
+		for expression in expressions
64
+			result = binaryThrow(position, result, expression.thrownType);
65
+		return result;
66
+	}
67
+}

+ 11
- 0
codemodel/src/generic/GenericParameterBound.zs View File

@@ -0,0 +1,11 @@
1
+public interface GenericParameterBound {
2
+	get isObjectType as bool;
3
+	
4
+	registerMembers(cache as LocalMemberCache, type as TypeMembers) as void;
5
+	
6
+	matches(cache as LocalMemberCache, type as ITypeID) as bool;
7
+	
8
+	withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as GenericParameterBound;
9
+	
10
+	accept<T>(visitor as GenericParameterBoundVisitor<T>) as T;
11
+}

+ 29
- 0
codemodel/src/generic/ParameterSuperBound.zs View File

@@ -0,0 +1,29 @@
1
+import .type.ITypeID;
2
+
3
+public class ParameterSuperBound {
4
+	val type as ITypeID : get;
5
+	
6
+	public this(type as ITypeID) {
7
+		this.type = type;
8
+	}
9
+	
10
+	public implements GenericParameterBound {
11
+		get isObjectType as bool
12
+			=> true;
13
+		
14
+		registerMembers(cache as LocalMemberCache, members as TypeMembers) {
15
+			// No members to register
16
+		}
17
+		
18
+		matches(cache as LocalMemberCache, type as ITypeID) as bool
19
+			=> cache[this.type].extendsOrImplements(type);
20
+
21
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as GenericParameterBound {
22
+			val translated = type.withGenericArguments(registry, arguments);
23
+			return translated === type ? this : new ParameterSuperBound(translated);
24
+		}
25
+
26
+		accept<T>(visitor as GenericParameterBoundVisitor<T>) as T
27
+			=> visitor.visitSuper(this);
28
+	}
29
+}

+ 29
- 0
codemodel/src/generic/ParameterTypeBound.zs View File

@@ -0,0 +1,29 @@
1
+import shared.CodePosition;
2
+import .type.ITypeID;
3
+
4
+public class ParameterTypeBound {
5
+	val position as CodePosition : get;
6
+	val type as ITypeID : get;
7
+	
8
+	public ParameterTypeBound(position as CodePosition, type as ITypeID) {
9
+		this.position = position;
10
+		this.type = type;
11
+	}
12
+	
13
+	public implements GenericParameterBound {
14
+		get isObjectType as bool
15
+			=> true;
16
+		
17
+		registerMembers(cache as LocalMemberCache, type as TypeMembers) as void
18
+			=> cache[this.type].copyMembersTo(position, type, FROM_TYPE_BOUNDS);
19
+
20
+		matches(cache as LocalMemberCache, type as ITypeID) as bool
21
+			=> cache[type].extendsOrImplements(this.type);
22
+
23
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as GenericParameterBound
24
+			=> new ParameterTypeBound(position, type.withGenericArguments(registry, arguments));
25
+
26
+		accept<T>(visitor as GenericParameterBoundVisitor<T>) as T
27
+			=> visitor.visitType(this);
28
+	}
29
+}

+ 37
- 0
codemodel/src/generic/TypeParameter.zs View File

@@ -0,0 +1,37 @@
1
+import shared.CodePosition;
2
+import .type.ITypeID;
3
+
4
+export class TypeParameter {
5
+	val position as CodePosition : get;
6
+	val name as string : get;
7
+	var bounds as GenericParameterBound[] : get, set;
8
+	
9
+	public this(position as CodePosition, name as string) {
10
+		this.position = position;
11
+		this.name = name;
12
+	}
13
+	
14
+	private this(position as CodePosition, name as string, bounds as GenericParameterBound[]) {
15
+		this.position = position;
16
+		this.name = name;
17
+		this.bounds = bounds;
18
+	}
19
+	
20
+	public get isObjectType as bool
21
+		=> bounds.contains(bound => bound.isObjectType);
22
+	
23
+	public matches(cache as LocalMemberCache, type as ITypeID) as bool
24
+		=> bounds.all(bound => bound.matches(cache, type));
25
+	
26
+	public withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as TypeParameter {
27
+		// TODO: think about this...
28
+		//List<GenericParameterBound> bounds = new ArrayList<>();
29
+		//for (GenericParameterBound bound : this.bounds)
30
+		//	bounds.add(bound.withGenericArguments(registry, arguments));
31
+		//return new TypeParameter(name, bounds);
32
+		return this;
33
+	}
34
+	
35
+	public as string
36
+		=> name + "@" + position.toShortString();
37
+}

+ 5
- 0
codemodel/src/generic/TypeParameterBoundVisitor.zs View File

@@ -0,0 +1,5 @@
1
+public interface GenericParameterBoundVisitor<T> {
2
+	public visitSuper(bound as ParameterSuperBound) as T;
3
+	
4
+	public visitType(bound as ParameterTypeBound) as T;
5
+}

+ 7
- 0
codemodel/src/scope/TypeScope.zs View File

@@ -0,0 +1,7 @@
1
+export interface TypeScope {
2
+	get typeRegistry as GlobalTypeRegistry;
3
+	get memberCache as LocalMemberCache;
4
+	
5
+	getTypeMembers(type as ITypeID) as TypeMembers
6
+		=> memberCache[type];
7
+}

+ 52
- 0
codemodel/src/type/ArrayTypeID.zs View File

@@ -0,0 +1,52 @@
1
+import .generic.TypeParameter;
2
+
3
+export class ArrayTypeID {
4
+	static val INT as ArrayTypeID : get = { BasicTypeID.INT, 1 };
5
+	static val CHAR as ArrayTypeID : get = { BasicTypeID.CHAR, 1 };
6
+	
7
+	val elementType as ITypeID : get;
8
+	val dimension as int : get;
9
+	
10
+	public this(elementType as ITypeID, dimension as ITypeID) {
11
+		this.elementType = elementType;
12
+		this.dimension = dimension;
13
+	}
14
+	
15
+	public implements ITypeID {
16
+		get unmodified as ArrayTypeID => this;
17
+		get isObjectType as bool => true;
18
+		get hasDefaultValue as bool => true;
19
+		
20
+		accept<T>(visitor as ITypeVisitor<T>) as T
21
+			=> visitor.visitArray(this);
22
+		
23
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ArrayTypeID
24
+			=> registry.getArray(elementType.withGenericArguments(registry, arguments), dimension);
25
+
26
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
27
+			=> elementType.hasInferenceBlockingTypeParameters(parameters);
28
+		
29
+		hashCode() as int {
30
+			var hash = 7;
31
+			hash = 79 * hash + elementType.hashCode();
32
+			hash = 79 * hash + dimension;
33
+			return hash;
34
+		}
35
+
36
+		==(other as ITypeID) as bool {
37
+			if this == other
38
+				return true;
39
+			if other !is ArrayTypeID
40
+				return false;
41
+			
42
+			val otherArray = other as ArrayTypeID;
43
+			return this.dimension == other.dimension && this.elementType === other.elementType;
44
+		}
45
+		
46
+		as string {
47
+			val result = new StringBuilder();
48
+			result << elementType as string << '[' << ','.times(dimension - 1) << ']';
49
+			return result as string;
50
+		}
51
+	}
52
+}

+ 52
- 0
codemodel/src/type/AssocTypeID.zs View File

@@ -0,0 +1,52 @@
1
+import .generic.TypeParameter;
2
+
3
+export class AssocTypeID {
4
+	val keyType as ITypeID : get;
5
+	val valueType as ITypeID : get;
6
+	
7
+	public this(keyType as ITypeID, valueType as ITypeID) {
8
+		this.keyType = keyType;
9
+		this.valueType = valueType;
10
+	}
11
+	
12
+	public implements ITypeID {
13
+		get unmodified as AssocTypeID => this;
14
+		get isObjectType as bool => true;
15
+		get hasDefaultValue as bool => true;
16
+		
17
+		accept<T>(visitor as ITypeVisitor<T>)
18
+			=> visitor.visitAssoc(this);
19
+		
20
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as AssocTypeID
21
+			=> registry.getAssociative(
22
+					keyType.withGenericArguments(registry, arguments),
23
+					valueType.withGenericArguments(registry, arguments));
24
+		
25
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
26
+			=> keyType.hasInferenceBlockingTypeParameters(parameters)
27
+				|| valueType.hasInferenceBlockingTypeParameters(parameters);
28
+
29
+		hashCode() as int {
30
+			val hash = 7;
31
+			hash = 29 * hash + keyType.hashCode();
32
+			hash = 29 * hash + valueType.hashCode();
33
+			return hash;
34
+		}
35
+		
36
+		==(other as ITypeID) {
37
+			if this == other
38
+				return true;
39
+			if other !is AssocTypeID
40
+				return false;
41
+			
42
+			val otherAssoc = other as AssocTypeID;
43
+			return this.keyType === other.keyType && this.valueType === other.valueType;
44
+		}
45
+		
46
+		as string {
47
+			val result = new StringBuilder();
48
+			result << valueType as string << '[' << keyType as string << ']';
49
+			return result as string;
50
+		}
51
+	}
52
+}

+ 47
- 0
codemodel/src/type/BasicTypeID.zs View File

@@ -0,0 +1,47 @@
1
+import .generic.TypeParameter;
2
+
3
+export enum BasicTypeID {
4
+	VOID("void"),
5
+	NULL("null"),
6
+	ANY("any"),
7
+	BOOL("bool"),
8
+	BYTE("byte"),
9
+	SBYTE("sbyte"),
10
+	SHORT("short"),
11
+	USHORT("ushort"),
12
+	INT("int"),
13
+	UINT("uint"),
14
+	LONG("long"),
15
+	ULONG("ulong"),
16
+	FLOAT("float"),
17
+	DOUBLE("double"),
18
+	CHAR("char"),
19
+	STRING("string"),
20
+	
21
+	UNDETERMINED("undetermined");
22
+	
23
+	static val HINT_BOOL as ITypeID[] : get = [BOOL];
24
+	
25
+	public val name as string : get;
26
+	
27
+	this(name as string) {
28
+		this.name = name;
29
+	}
30
+	
31
+	public implements ITypeID {
32
+		get unmodified as ITypeID => this;
33
+		get isObjectType as bool => this in [ANY, STRING];
34
+		get hasDefaultValue as bool => true;
35
+		
36
+		accept<T>(visitor as ITypeVisitor<T>) as T
37
+			=> visitor.visitBasic(this);
38
+		
39
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as BasicTypeID
40
+			=> this;
41
+
42
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[])
43
+			=> false;
44
+		
45
+		as string => name;
46
+	}
47
+}

+ 48
- 0
codemodel/src/type/ConstTypeID.zs View File

@@ -0,0 +1,48 @@
1
+public class ConstTypeID {
2
+	val baseType as ITypeID : get;
3
+	
4
+	public this(baseType as ITypeID) {
5
+		this.baseType = baseType;
6
+	}
7
+	
8
+	public implements ITypeID {
9
+		get unmodified as ITypeID => baseType.unmodified;
10
+		get isConst as bool => true;
11
+		get isOptional as bool => baseType.isOptional;
12
+		get isObjectType as bool => baseType.isObjectType;
13
+		get hasDefaultValue as bool => baseType.hasDefaultValue;
14
+		
15
+		accept<T>(visitor as ITypeVisitor<T>)
16
+			=> visitor.visitConst(this);
17
+		
18
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
19
+			=> registry.getModified(TypeMembers.MODIFIER_CONST, baseType.withGenericArguments(registry, arguments));
20
+		
21
+		isDefinition(definition as HighLevelDefinition) as bool
22
+			=> baseType.isDefinition(definition);
23
+		
24
+		unwrap() as ITypeID
25
+			=> baseType;
26
+
27
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
28
+			=> baseType.hasInferenceBlockingTypeParameters(parameters);
29
+
30
+		hashCode() as int {
31
+			var hash = 3;
32
+			hash = 79 * hash + baseType.hashCode();
33
+			return hash;
34
+		}
35
+
36
+		==(other as ITypeID) {
37
+			if this == other
38
+				return true;
39
+			if other !is ConstTypeID
40
+				return false;
41
+			
42
+			val otherConst = other as ConstTypeID;
43
+			return this.baseType == other.baseType;
44
+		}
45
+		
46
+		as string => "const " + baseType as string;
47
+	}
48
+}

+ 146
- 0
codemodel/src/type/DefinitionTypeID.zs View File

@@ -0,0 +1,146 @@
1
+import .HighLevelDefinition;
2
+import .definition.EnumDefinition;
3
+import .definition.VariantDefinition;
4
+import .generic.TypeParameter;
5
+
6
+export class DefinitionTypeID {
7
+	public static forType(definition as HighLevelDefinition) as DefinitionTypeID {
8
+		if definition.genericParameters.length > 0
9
+			return panic("Definition has type arguments!");
10
+		
11
+		return { definition, [] };
12
+	}
13
+	
14
+	static val NO_OUTER_ENTRIES as OuterTypeEntry[] = new OuterTypeEntry[](0);
15
+	
16
+	val definition as HighLevelDefinition : get;
17
+	val typeParameters as ITypeID[] : get;
18
+	val outerTypeEntries as OuterTypeEntry[] : get;
19
+	var superType as ITypeID? : get, set;
20
+	// for nonstatic inner classes of generic types, contains the type parameters for the outer class(es)
21
+	val outerTypeParameters as ITypeID[TypeParameter];
22
+	
23
+	public this(definition as HighLevelDefinition) {
24
+		this.definition = definition;
25
+		this.typeParameters = [];
26
+		this.superType = definition.superType;
27
+		this.outerTypeParameters = {};
28
+		this.outerTypeEntries = NO_OUTER_ENTRIES;
29
+	}
30
+	
31
+	public this(definition as HighLevelDefinition, typeParameters as ITypeID[]) {
32
+		this(definition, typeParameters, {});
33
+	}
34
+	
35
+	// For inner classes of generic outer classes
36
+	public this(
37
+			definition as HighLevelDefinition,
38
+			typeParameters as ITypeID[],
39
+			outerTypeParameters as ITypeID[TypeParameter]) {
40
+		this.definition = definition;
41
+		this.typeParameters = typeParameters;
42
+		this.outerTypeParameters = outerTypeParameters;
43
+		
44
+		if outerTypeParameters.empty {
45
+			this.outerTypeEntries = NO_OUTER_ENTRIES;
46
+		} else {
47
+			this.outerTypeEntries = new OuterTypeEntry[](outerTypeParameters.size);
48
+			val index = 0;
49
+			for parameter, type in outerTypeParameters
50
+				outerTypeEntries[index++] = new OuterTypeEntry(parameter, type);
51
+			this.outerTypeEntries.sort(outerTypeEntries, (a, b) => a.parameter.name.compareTo(b.parameter.name));
52
+		}
53
+	}
54
+	
55
+	public get hasTypeParameters as bool
56
+		=> typeParameters.length > 0;
57
+	
58
+	public init(registry as GlobalTypeRegistry) as void {
59
+		val superType = definition.superType;
60
+		if superType != null && hasTypeParameters() {
61
+			val genericSuperArguments = new ITypeID[TypeParameter];
62
+			for i, typeParameter in typeParameters
63
+				genericSuperArguments[definition.genericParameters[i]] = typeParameter;
64
+			superType = definition.superType.withGenericArguments(registry, genericSuperArguments);
65
+		}
66
+		this.superType = superType;
67
+	}
68
+	
69
+	public implements ITypeID {
70
+		get unmodified as ITypeID => this;
71
+		get isObjectType as bool => true;
72
+		get isEnum as bool => definition is EnumDefinition;
73
+		get isVariant as bool => definition is VariantDefinition;
74
+		get hasDefaultValue => definition.hasEmptyConstructor();
75
+		
76
+		accept<T>(visitor as ITypeVisitor<T>) as T
77
+			=> visitor.visitDefinition(this);
78
+		
79
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID {
80
+			if !hasTypeParameters && outerTypeParameters.empty
81
+				return this;
82
+			
83
+			val instancedArguments = typeParameters.map(parameter => parameter.withGenericArguments(registry, arguments));
84
+			val instancedOuter = outerTypeParameters.mapValues(value => value.withGenericArguments(registry, arguments));
85
+			return registry.getForDefinition(definition, instancedArguments, instancedOuter);
86
+		}
87
+		
88
+		accept<T>(visitor as ITypeVisitor<T>) as T
89
+			=> visitor.visitDefinition(this);
90
+		
91
+		isDefinition(definition as HighLevelDefinition) as bool
92
+			=> definition === this.definition;
93
+		
94
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
95
+			=> typeParameters.contains(parameter => parameter.hasInferenceBlockingTypeParameters(parameters))
96
+				|| (superType != null && superType.hasInferenceBlockingTypeParameters(parameters));
97
+
98
+		hashCode() as int {
99
+			val hash = 7;
100
+			hash = 97 * hash + definition.hashCode();
101
+			hash = 97 * hash + typeParameters.hashCode();
102
+			hash = 97 * hash + outerTypeEntries.hashCode();
103
+			return hash;
104
+		}
105
+
106
+		==(other as ITypeID) {
107
+			if this === other
108
+				return true;
109
+			
110
+			if other !is DefinitionTypeID
111
+				return false;
112
+			
113
+			val otherDefinition = other as DefinitionTypeID;
114
+			return this.definition == otherDefinition.definition
115
+					&& this.typeParameters.deepEquals(otherDefinition.typeParameters)
116
+					&& this.outerTypeEntries.deepEquals(otherDefinition.outerTypeEntries);
117
+		}
118
+		
119
+		as string {
120
+			if typeParameters.empty
121
+				return definition.name;
122
+			
123
+			val result = new StringBuilder();
124
+			result << definition.name;
125
+			result << '<' << typeParameters.map(parameter => parameter as string).join(', ') << '>';
126
+			return result as string;
127
+		}
128
+	}
129
+	
130
+	private struct OuterTypeEntry {
131
+		val parameter as TypeParameter;
132
+		val type as ITypeID;
133
+		
134
+		public implements Hashable<OuterTypeEntry> {
135
+			hashCode() as int {
136
+				var hash = 3;
137
+				hash = 11 * hash + Objects.hashCode(this.parameter);
138
+				hash = 11 * hash + Objects.hashCode(this.type);
139
+				return hash;
140
+			}
141
+			
142
+			==(other as OuterTypeEntry)
143
+				=> this === obj || (this.parameter === other.parameter && this.type === other.type);
144
+		}
145
+	}
146
+}

+ 53
- 0
codemodel/src/type/FunctionTypeID.zs View File

@@ -0,0 +1,53 @@
1
+import .FunctionHeader;
2
+import .FunctionParameter;
3
+import .generic.TypeParameter;
4
+
5
+/**
6
+ *
7
+ * @author Hoofdgebruiker
8
+ */
9
+public class FunctionTypeID {
10
+	val header as FunctionHeader : get;
11
+	
12
+	public this(header as FunctionHeader) {
13
+		this.header = header;
14
+	}
15
+	
16
+	public implements ITypeID {
17
+		get unmodified as FunctionTypeID => this;
18
+		get isObjectType as bool => true;
19
+		get hasDefaultValue as bool => false;
20
+		
21
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
22
+			=> registry.getFunction(header.withGenericArguments(registry, arguments));
23
+		
24
+		accept<T>(visitor as ITypeVisitor<T>) as T
25
+			=> visitor.visitFunction(this);
26
+
27
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[])
28
+			=> header.hasInferenceBlockingTypeParameters(parameters);
29
+
30
+		hashCode() as int {
31
+			var hash = 5;
32
+			hash = 71 * hash + header.returnType.hashCode();
33
+			hash = 71 * hash + header.parameters.hashCode();
34
+			hash = 71 * hash + header.typeParameters.hashCode();
35
+			return hash;
36
+		}
37
+
38
+		==(other as ITypeID) {
39
+			if this === other
40
+				return true;
41
+			if other !is FunctionTypeID
42
+				return false;
43
+			
44
+			val otherFunction = other as FunctionTypeID;
45
+			return this.header.returnType == otherFunction.header.returnType
46
+					&& this.header.parameters == otherFunction.header.parameters
47
+					&& this.header.typeParameters == otherFunction.header.typeParameters;
48
+		}
49
+		
50
+		as string
51
+			=> header as string;
52
+	}
53
+}

+ 50
- 0
codemodel/src/type/GenericMapTypeID.zs View File

@@ -0,0 +1,50 @@
1
+import .generic.TypeParameter;
2
+
3
+public class GenericMapTypeID {
4
+	val value as ITypeID : get;
5
+	val keys as TypeParameter[] : get;
6
+	
7
+	public this(value as ITypeID, keys as TypeParameter[]) {
8
+		this.value = value;
9
+		this.keys = keys;
10
+	}
11
+	
12
+	public implements ITypeID {
13
+		get unmodified as ITypeID => this;
14
+		get hasDefaultValue as bool => true;
15
+		get isObjectType as bool => true;
16
+
17
+		accept<T>(visitor as ITypeVisitor<T>) as T
18
+			=> visitor.visitGenericMap(this);
19
+
20
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
21
+			=> registry.getGenericMap(value.withGenericArguments(registry, arguments), keys);
22
+
23
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
24
+			=> value.hasInferenceBlockingTypeParameters(parameters);
25
+		
26
+		as string {
27
+			val result = new StringBuilder();
28
+			result << value as string;
29
+			result << '[<' << keys.map(key => key as string).join(', ') << '>]';
30
+			return result as string;
31
+		}
32
+
33
+		hashCode() as int {
34
+			var hash = 5;
35
+			hash = 97 * hash + this.value.hashCode();
36
+			hash = 97 * hash + this.keys.hashCode();
37
+			return hash;
38
+		}
39
+		
40
+		==(other as ITypeID) as bool {
41
+			if this === other
42
+				return true;
43
+			if other !is GenericMapTypeID
44
+				return false;
45
+			
46
+			val otherMap = other as GenericMapTypeID;
47
+			return this.value == other.value && this.keys == other.keys;
48
+		}
49
+	}
50
+}

+ 46
- 0
codemodel/src/type/GenericTypeID.zs View File

@@ -0,0 +1,46 @@
1
+import .generic.TypeParameter;
2
+import .type.member.LocalMemberCache;
3
+
4
+public class GenericTypeID {
5
+	val parameter as TypeParameter : get;
6
+
7
+	public this(parameter as TypeParameter) {
8
+		this.parameter = parameter;
9
+	}
10
+	
11
+	public matches(cache as LocalMemberCache, type as ITypeID) as bool
12
+		=> parameter.matches(cache, type);
13
+	
14
+	public implements ITypeID {
15
+		get unmodified as ITypeID => this;
16
+		get hasDefaultValue as bool => false;
17
+		get isObjectType as bool => parameter.isObjectType;
18
+		
19
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
20
+			=> arguments.getOrDefault(parameter, this);
21
+
22
+		accept<T>(visitor as ITypeVisitor<T>)
23
+			=> visitor.visitGeneric(this);
24
+		
25
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
26
+			=> parameter in parameters;
27
+		
28
+		hashCode() as int {
29
+			var hash = 7;
30
+			hash = 47 * hash + parameter.hashCode();
31
+			return hash;
32
+		}
33
+		
34
+		==(other as ITypeID) as bool {
35
+			if this === other
36
+				return true;
37
+			if this !is GenericTypeID
38
+				return false;
39
+			
40
+			val otherGeneric = other as GenericTypeID;
41
+			return this.parameter === otherGeneric.parameter;
42
+		}
43
+		
44
+		as string => parameter as string;
45
+	}
46
+}

+ 144
- 0
codemodel/src/type/GlobalTypeRegistry.zs View File

@@ -0,0 +1,144 @@
1
+import .FunctionHeader;
2
+import .HighLevelDefinition;
3
+import .definition.FunctionDefinition;
4
+import .definition.ZSPackage;
5
+import .generic.TypeParameter;
6
+import .type.members.TypeMembers;
7
+
8
+/**
9
+ *
10
+ * @author Hoofdgebruiker
11
+ */
12
+public class GlobalTypeRegistry {
13
+	val arrayTypes as ArrayTypeID[ArrayTypeID] = new ArrayTypeID[ArrayTypeID];
14
+	val assocTypes as AssocTypeID[AssocTypeID] = new AssocTypeID[AssocTypeID];
15
+	val genericMapTypes as GenericMapTypeID[GenericMapTypeID] = new GenericMapTypeID[GenericMapTypeID];
16
+	val iteratorTypes as IteratorTypeID[IteratorTypeID] = new IteratorTypeID[IteratorTypeID];
17
+	val functionTypes as FunctionTypeID[FunctionTypeID] = new FunctionTypeID[FunctionTypeID];
18
+	val rangeTypes as RangeTypeID[RangeTypeID] = new RangeTypeID[RangeTypeID];
19
+	val definitionTypes as DefinitionTypeID[DefinitionTypeID] = new DefinitionTypeID[DefinitionTypeID];
20
+	val genericTypes as GenericTypeID[GenericTypeID] = new GenericTypeID[GenericTypeID];
21
+	
22
+	val constTypes as ConstTypeID[ITypeID] = new ConstTypeID[ITypeID];
23
+	val optionalTypes as OptionalTypeID[ITypeID] = new OptionalTypeID[ITypeID];
24
+	
25
+	val stdlib as ZSPackage : get;
26
+	
27
+	public this(stdlib as ZSPackage) {
28
+		this.stdlib = stdlib;
29
+		
30
+		arrayTypes[ArrayTypeID.INT] = ArrayTypeID.INT;
31
+		arrayTypes[ArrayTypeID.CHAR] = ArrayTypeID.CHAR;
32
+		
33
+		rangeTypes[RangeTypeID.INT] = RangeTypeID.INT;
34
+	}
35
+	
36
+	public getArray(baseType as ITypeID, dimension as int) as ArrayTypeID {
37
+		val type = new ArrayTypeID(baseType, dimension);
38
+		if type in arrayTypes
39
+			return arrayTypes[type];
40
+		
41
+		arrayTypes[type] = type;
42
+		return type;
43
+	}
44
+	
45
+	public getAssociative(keyType as ITypeID, valueType as ITypeID) as AssocTypeID {
46
+		val type = new AssocTypeID(keyType, valueType);
47
+		if type in assocTypes
48
+			return assocTypes[type];
49
+		
50
+		assocTypes[type] = type;
51
+		return type;
52
+	}
53
+	
54
+	public getGenericMap(valueType as ITypeID, keys as TypeParameter[]) as GenericMapTypeID {
55
+		val type = new GenericMapTypeID(valueType, keys);
56
+		if type in genericMapTypes
57
+			return genericMapTypes[type];
58
+			
59
+		genericMapTypes[type] = type;
60
+		return type;
61
+	}
62
+	
63
+	public getIterator(loopTypes as ITypeID[]) as IteratorTypeID {
64
+		val type = new IteratorTypeID(loopTypes);
65
+		if type in iteratorTypes
66
+			return iteratorTypes[type];
67
+		
68
+		iteratorTypes[type] = type;
69
+		return type;
70
+	}
71
+	
72
+	public getFunction(header as FunctionHeader) as FunctionTypeID {
73
+		val type = new FunctionTypeID(header);
74
+		if type in functionTypes
75
+			return functionTypes[type];
76
+		
77
+		functionTypes[type] = type;
78
+		return type;
79
+	}
80
+	
81
+	public getRange(from as ITypeID, to as ITypeID) as RangeTypeID {
82
+		val type = new RangeTypeID(from, to);
83
+		if type in rangeTypes
84
+			return rangeTypes[type];
85
+		
86
+		rangeTypes[type] = type;
87
+		return type;
88
+	}
89
+	
90
+	public getGeneric(parameter as TypeParameter) as GenericTypeID {
91
+		val type = new GenericTypeID(parameter);
92
+		if type in genericTypes
93
+			return genericTypes[type];
94
+		
95
+		genericTypes[type] = type;
96
+		return type;
97
+	}
98
+	
99
+	public getForDefinition(definition as HighLevelDefinition, genericArguments... as ITypeID) as DefinitionTypeID
100
+		=> getForDefinition(definition, genericArguments, {});
101
+	
102
+	public getForDefinition(definition as HighLevelDefinition, typeParameters as ITypeID[], outerInstance as ITypeID[TypeParameter]) as DefinitionTypeID {
103
+		val type = (definition is FunctionDefinition)
104
+			? new DefinitionTypeID(definition)
105
+			: new DefinitionTypeID(definition, typeParameters, outerInstance);
106
+		
107
+		if type in definitionTypes
108
+			return definitionTypes[type];
109
+		
110
+		definitionTypes[type] = type;
111
+		type.init(this);
112
+		return type;
113
+	}
114
+	
115
+	private getConst(original as ITypeID) as ConstTypeID {
116
+		if original in constTypes
117
+			return constTypes[original];
118
+		
119
+		val type = new ConstTypeID(original);
120
+		constTypes[original] = type;
121
+		return type;
122
+	}
123
+	
124
+	public getOptional(original as ITypeID) as OptionalTypeID {
125
+		if original in optionalTypes
126
+			return optionalTypes[original];
127
+		
128
+		val type = new OptionalTypeID(original);
129
+		optionalTypes[original] = type;
130
+		return type;
131
+	}
132
+	
133
+	public getModified(modifiers as int, type as ITypeID) as ITypeID {
134
+		if modifiers == 0
135
+			return type;
136
+		
137
+		if (modifiers & TypeMembers.MODIFIER_OPTIONAL) > 0
138
+			return getModified(modifiers & ~TypeMembers.MODIFIER_OPTIONAL, getOptional(type));
139
+		if (modifiers & MODIFIER_CONST) > 0
140
+			return getModified(modifiers & ~TypeMembers.MODIFIER_CONST, getConst(type));
141
+		
142
+		return panic("Invalid modifier: " + modifiers);
143
+	}
144
+}

+ 152
- 0
codemodel/src/type/ITypeID.zs View File

@@ -0,0 +1,152 @@
1
+public interface ITypeID : Hashable<ITypeID> {
2
+	public get unmodified as ITypeID => this;
3
+	public get superType as ITypeID? => null;
4
+	public get isOptional as bool => false;
5
+	public get optionalBase => this;
6
+	public get isConst as bool => false;
7
+	public get unwrap as ITypeID => this;
8
+	public get hasDefaultValue as bool;
9
+	public get isObjectType as bool;
10
+	public get isEnum as bool => false;
11
+	public get isVariant as bool => false;
12
+	
13
+	public isDefinition(definition as HighLevelDefinition) as bool
14
+		=> false;
15
+	
16
+	public accept<T>(visitor as ITypeVisitor<T>) as T;
17
+	
18
+	public withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID;
19
+	
20
+	public hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool;
21
+	
22
+	public as string;
23
+	
24
+	// Infers type parameters for this type so it matches with targetType
25
+	// returns false if that isn't possible
26
+	public final inferTypeParameters(
27
+			cache as LocalMemberCache,
28
+			targetType as ITypeID,
29
+			mapping as ITypeID[TypeParameter]) as bool
30
+		=> accept(new MatchingTypeVisitor(cache, targetType, mapping));
31
+	
32
+	public static class MatchingTypeVisitor {
33
+		val type as ITypeID;
34
+		val mapping as ITypeID[TypeParameter];
35
+		val cache as LocalMemberCache;
36
+		
37
+		public this(cache as LocalMemberCache, type as ITypeID, mapping as ITypeID[TypeParameter]) {
38
+			this.type = type;
39
+			this.mapping = mapping;
40
+			this.cache = cache;
41
+		}
42
+	
43
+		public implements ITypeVisitor<bool> {
44
+			visitBasic(basic as BasicTypeID) as bool
45
+				=> basic == type;
46
+
47
+			visitArray(aray as ArrayTypeID) as bool {
48
+				if type !is ArrayTypeID
49
+					return false;
50
+				
51
+				val arrayType = type as ArrayTypeID;
52
+				if arrayType.dimension != array.dimension
53
+					return false;
54
+				
55
+				return matches(arrayType.elementType, array.elementType);
56
+			}
57
+
58
+			visitAssoc(assoc as AssocTypeID) as bool {
59
+				if type !is AssocTypeID
60
+					return false;
61
+					
62
+				val assocType = type as AssocTypeID;
63
+				return matches(assoc.keyType, assocType.keyType)
64
+						&& matches(assoc.valueType, assocType.valueType);
65
+			}
66
+			
67
+			visitIterator(iterator as IteratorTypeID) as bool {
68
+				if type !is IteratorTypeID
69
+					return false;
70
+					
71
+				val iteratorType = type as IteratorTypeID;
72
+				if iteratorType.iteratorTypes.length != iterator.iteratorTypes.length
73
+					return false;
74
+				
75
+				return iteratorType.iteratorTypes
76
+					.all((i, type) => matches(type, iterator.iteratorTypes[i]));
77
+			}
78
+
79
+			visitFunction(func as FunctionTypeID) {
80
+				if type !is FunctionTypeID
81
+					return false;
82
+				
83
+				val functionType = type as FunctionTypeID;
84
+				if functionType.header.parameters.length != func.header.parameters.length
85
+					return false;
86
+				
87
+				return matches(functionType.header.returnType, func.header.returnType)
88
+					&& functionType.header.parameters.all(
89
+						(i, parameter) => matches(parameter.type, func.header.parameters[i].type));
90
+			}
91
+
92
+			visitDefinition(definition as DefinitionTypeID) as bool {
93
+				if type !is DefinitionTypeID
94
+					return false;
95
+				
96
+				val definitionType = type as DefinitionTypeID;
97
+				if definitionType.definition != definition.definition
98
+					return false;
99
+				
100
+				return definition.typeParameters == null
101
+					|| definitionType.typeParameters.all(
102
+						(i, typeParameter) => matches(typeParameter, definition.typeParameters[i]));
103
+			}
104
+
105
+			visitGeneric(generic as GenericTypeID) as void {
106
+				if generic.parameter in mapping {
107
+					return mapping[generic.parameter] == type;
108
+				} else if generic.matches(cache, type) {
109
+					mapping[generic.parameter] = type;
110
+					return true;
111
+				} else {
112
+					return false;
113
+				}
114
+			}
115
+
116
+			visitRange(range as RangeTypeID) as bool {
117
+				if type !is RangeTypeID
118
+					return false;
119
+				
120
+				val rangeType = type as RangeTypeID;
121
+				return matches(rangeType.from, range.from) && matches(rangeType.to, range.to);
122
+			}
123
+
124
+			visitConst(type as ConstTypeID) as bool {
125
+				if this.type !is ConstTypeID
126
+					return false;
127
+					
128
+				val constType = this.type as ConstTypeID;
129
+				return matches(constType.baseType, type.baseType);
130
+			}
131
+
132
+			visitOptional(optional as OptionalTypeID) as bool {
133
+				if this.type !is ConstTypeID
134
+					return false;
135
+				
136
+				val optionalType = this.type as OptionalTypeID;
137
+				return matches(optionalType.baseType, optional.baseType);
138
+			}
139
+
140
+			visitGenericMap(map as GenericMapTypeID) as bool {
141
+				return map == type; // TODO: improve this
142
+			}
143
+			
144
+			private matches(type as ITypeID, pattern as ITypeID) as bool {
145
+				if type == pattern
146
+					return true;
147
+				
148
+				return pattern.accept(new MatchingTypeVisitor(cache, type, mapping));
149
+			}
150
+		}
151
+	}
152
+}

+ 36
- 0
codemodel/src/type/ITypeVisitor.zs View File

@@ -0,0 +1,36 @@
1
+export interface ITypeVisitor<T> {
2
+	visitBasic(basic as BasicTypeID) as T
3
+		=> visitOther(basic);
4
+	
5
+	visitArray(array as ArrayTypeID) as T
6
+		=> visitOther(array);
7
+	
8
+	visitAssoc(assoc as AssocTypeID) as T
9
+		=> visitOther(assoc);
10
+	
11
+	visitGenericMap(map as GenericMapTypeID) as T
12
+		=> visitOther(map);
13
+	
14
+	visitIterator(iterator as IteratorTypeID) as T
15
+		=> visitOther(iterator);
16
+	
17
+	visitFunction(func as FunctionTypeID) as T
18
+		=> visitOther(func);
19
+	
20
+	visitDefinition(definition as DefinitionTypeID) as T
21
+		=> visitOther(definition);
22
+	
23
+	visitGeneric(generic as GenericTypeID) as T
24
+		=> visitOther(generic);
25
+	
26
+	visitRange(range as RangeTypeID) as T
27
+		=> visitOther(generic);
28
+	
29
+	visitConst(type as ConstTypeID) as T
30
+		=> visitOther(type);
31
+	
32
+	visitOptional(optional as OptionalTypeID) as T
33
+		=> visitOther(optional);
34
+	
35
+	visitOther(type as ITypeID) as T;
36
+}

+ 40
- 0
codemodel/src/type/IteratorTypeID.zs View File

@@ -0,0 +1,40 @@
1
+import .generic.TypeParameter;
2
+
3
+public class IteratorTypeID {
4
+	val iteratorTypes as ITypeID[] : get;
5
+	
6
+	public this(iteratorTypes as ITypeID[]) {
7
+		this.iteratorTypes = iteratorTypes;
8
+	}
9
+	
10
+	public implements ITypeID {
11
+		get unmodified as ITypeID => this;
12
+		get isObjectType as bool => true;
13
+		get hasDefaultValue as bool => false;
14
+		
15
+		accept<T>(visitor as ITypeVisitor<T>) as T
16
+			=> visitor.visitIterator(visitor);
17
+		
18
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
19
+			=> registry.getIterator(iteratorTypes.map(type => type.withGenericArguments(registry, arguments)));
20
+
21
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
22
+			=> iteratorTypes.contains(type => type.hasInferenceBlockingTypeParameters(parameters));
23
+
24
+		hashCode() as int {
25
+			var hash = 5;
26
+			hash = 13 * hash + iteratorTypes.hashCode();
27
+			return hash;
28
+		}
29
+
30
+		==(other as ITypeID) as bool {
31
+			if this === other
32
+				return true;
33
+			if other !is IteratorTypeID
34
+				return false;
35
+			
36
+			val otherIterator = other as IteratorTypeID;
37
+			return iteratorTypes == other.iteratorTypes;
38
+		}
39
+	}
40
+}

+ 52
- 0
codemodel/src/type/OptionalTypeID.zs View File

@@ -0,0 +1,52 @@
1
+import .HighLevelDefinition;
2
+import .type.member.TypeMembers;
3
+import .generic.TypeParameter;
4
+
5
+export class OptionalTypeID {
6
+	val baseType as ITypeID : get;
7
+	
8
+	public this(baseType as ITypeID) {
9
+		this.baseType = baseType;
10
+	}
11
+	
12
+	public implements ITypeID {
13
+		get unmodified as ITypeID => baseType.unmodified;
14
+		get isOptional as bool => true;
15
+		get optionalBase as ITypeID => baseType;
16
+		get isObjectType as bool => baseType.isObjectType;
17
+		get hasDefaultValue as bool => true;
18
+		
19
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
20
+			=> registry.getModified(TypeMembers.MODIFIER_OPTIONAL, baseType.withGenericArguments(registry, arguments));
21
+
22
+		accept<T>(visitor as ITypeVisitor<T>)
23
+			=> visitor.visitOptional(this);
24
+		
25
+		isDefinition(definition as HighLevelDefinition) as bool
26
+			=> baseType.isDefinition(definition);
27
+		
28
+		unwrap() as ITypeID
29
+			=> baseType;
30
+		
31
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
32
+			=> baseType.hasInferenceBlockingTypeParameters(parameters);
33
+
34
+		hashCode() as int {
35
+			val hash = 7;
36
+			hash = 29 * hash + baseType.hashCode();
37
+			return hash;
38
+		}
39
+
40
+		==(other as ITypeID) as bool {
41
+			if this === other
42
+				return true;
43
+			if this !is OptionalTypeID
44
+				return false;
45
+			
46
+			val otherOptional = other as OptionalTypeID;
47
+			return this.baseType == otherOptional.baseType;
48
+		}
49
+		
50
+		as string => baseType as string + '?';
51
+	}
52
+}

+ 50
- 0
codemodel/src/type/RangeTypeID.zs View File

@@ -0,0 +1,50 @@
1
+import .generic.TypeParameter;
2
+
3
+public class RangeTypeID {
4
+	static val INT as RangeTypeID : get = new RangeTypeID(BasicTypeID.INT, BasicTypeID.INT);
5
+	
6
+	val from as ITypeID : get;
7
+	val to as ITypeID : get;
8
+	
9
+	public this(from as ITypeID, to as ITypeID) {
10
+		this.from = from;
11
+		this.to = to;
12
+	}
13
+	
14
+	public implements ITypeID {
15
+		get unmodified as RangeTypeID => this;
16
+		get isObjectType as bool => false;
17
+		get hasDefaultValue as bool => false;
18
+		
19
+		withGenericArguments(registry as GlobalTypeRegistry, arguments as ITypeID[TypeParameter]) as ITypeID
20
+			=> registry.getRange(
21
+					from.withGenericArguments(registry, arguments),
22
+					to.withGenericArguments(registry, arguments));
23
+
24
+		accept<T>(visitor as ITypeVisitor<T>) as TypeParamete
25
+			=> visitor.visitRange(this);
26
+		
27
+		hasInferenceBlockingTypeParameters(parameters as TypeParameter[]) as bool
28
+			=> from.hasInferenceBlockingTypeParameters(parameters)
29
+				|| to.hasInferenceBlockingTypeParameters(parameters);
30
+		
31
+		hashCode() as int {
32
+			var hash = 5;
33
+			hash = 89 * hash + from.hashCode();
34
+			hash = 89 * hash + to.hashCode();
35
+			return hash;
36
+		}
37
+		
38
+		==(other as ITypeID) as bool {
39
+			if this === other
40
+				return true;
41
+			if this !is RangeTypeID
42
+				return false;
43
+				
44
+			val otherRange = other as RangeTypeID;
45
+			return from === other.from && to === other.to;
46
+		}
47
+		
48
+		as string => from as string + ' .. ' + to as string;
49
+	}
50
+}

+ 30
- 0
codemodel/src/type/member/LocalMemberCache.zs View File

@@ -0,0 +1,30 @@
1
+import .AccessScope;
2
+import .definition.ExpansionDefinition;
3
+import .type.GlobalTypeRegistry;
4
+import .type.ITypeID;
5
+
6
+export class LocalMemberCache {
7
+	val access as AccessScope : get;
8
+	val registry as GlobalTypeRegistry : get;
9
+	val types as TypeMembers[ITypeID];
10
+	val expansions as ExpansionDefinition[] : get;
11
+	
12
+	public this(
13
+			scope as AccessScope,
14
+			registry as GlobalTypeRegistry,
15
+			expansions as ExpansionDefinition[]) {
16
+		this.access = access;
17
+		this.registry = registry;
18
+		this.expansions = expansions;
19
+	}
20
+	
21
+	public [](type as ITypeID) as TypeMembers {
22
+		if type in types
23
+			return types[type];
24
+		
25
+		var members = new TypeMembers(this, type);
26
+		members.type.accept(new TypeMemberBuilder(registry, members, this));
27
+		types[type] = members;
28
+		return members;
29
+	}
30
+}

+ 14
- 0
codemodel/src/type/member/TypeMember.zs View File

@@ -0,0 +1,14 @@
1
+export struct TypeMember<T : DefinitionMember> {
2
+	val priority as TypeMemberPriority : get;
3
+	val member as T : get;
4
+	
5
+	public resolve(other as TypeMember<T>) as TypeMember<T> {
6
+		if priority == other.priority {
7
+			if priority == SPECIFIED
8
+				throw new CompileException(other.member.getPosition(), MEMBER_DUPLICATE, "Duplicate " + other.member.describe());
9
+			return this;
10
+		}
11
+		
12
+		return priority < other.priority ? other : this;
13
+	}
14
+}

+ 7
- 0
codemodel/src/type/member/TypeMemberPriority.zs View File

@@ -0,0 +1,7 @@
1
+public enum TypeMemberPriority {
2
+	BUILTIN_DEFAULT,
3
+	FROM_TYPE_BOUNDS,
4
+	INTERFACE,
5
+	INHERITED,
6
+	SPECIFIED
7
+}

+ 10
- 0
parser/module.json View File

@@ -0,0 +1,10 @@
1
+{
2
+	"package": "org.openzen.zencode.parser",
3
+	"host": "universal",
4
+	"dependencies": [
5
+		"collections",
6
+		"io",
7
+		"shared",
8
+		"codemodel"
9
+	]
10
+}

+ 111
- 0
parser/src/lexer/CharStream.zs View File

@@ -0,0 +1,111 @@
1
+public class CharStream {
2
+    val data as char[];
3
+    var index as int;
4
+
5
+    /**
6
+     * Constructs a character stream from a data string.
7
+     *
8
+     * @param data data string
9
+     */
10
+    public this(data as string) {
11
+        this.data = data.characters;
12
+        index = 0;
13
+    }
14
+	
15
+	public peek() as char
16
+		=> index == data.length ? 0 : data[index];
17
+	
18
+    /**
19
+     * Checks if the next character in the stream equals the specified character.
20
+     *
21
+     * @param ch checked character
22
+     * @return true if the next character equals the specified character
23
+     */
24
+    public peek(ch as char) as bool
25
+		=> index == data.length ? false : data[index] == ch;
26
+	
27
+    /**
28
+     * Processes an optional character. If the next character does not equal
29
+     * the specified character, nothing will happen.
30
+     *
31
+     * @param ch checked character
32
+     * @return true if the next character equals the specified character
33
+     */
34
+    public optional(ch as char) as bool {
35
+        if index == data.length
36
+			return false;
37
+		
38
+        if data[index] == ch {
39
+            index++;
40
+            return true;
41
+        } else {
42
+            return false;
43
+        }
44
+    }
45
+
46
+    /**
47
+     * Processes an optional range of characters. If the next character is not
48
+     * within the specified bounds, nothing will happen.
49
+     *
50
+     * @param from lower character value bound, inclusive
51
+     * @param to upper character value bound, inclusive
52
+     * @return the processed character
53
+     */
54
+    public optional(range as char..char) as char {
55
+        if index == data.length
56
+			return 0;
57
+		
58
+        if data[index] >= range.from && data[index] <= range.to {
59
+            return data[index++];
60
+        } else {
61
+            return 0;
62
+        }
63
+    }
64
+
65
+    /**
66
+     * Processes a required character. If the next character does not equal the
67
+     * specified character, an IllegalArgumentException is thrown.
68
+     *
69
+     * @param ch required character
70
+     */
71
+    public required(ch as char) as void {
72
+        if index >= data.length
73
+            throw new IllegalArgumentException("Expected " + ch + " but got EOF");
74
+		
75
+		if data[index] != ch
76
+            throw new IllegalArgumentException("Expected " + ch + " but got " + data[index]);
77
+		
78
+        index++;
79
+    }
80
+
81
+    /**
82
+     * Processes a required character range. If the next character is not within
83
+     * the specified bounds, an IllegalArgumentException is thrown.
84
+     *
85
+     * @param from lower character value bound, inclusive
86
+     * @param to upper character value bound, inclusive
87
+     * @return the processed character
88
+     */
89
+    public required(range as char..char) as char {
90
+        if data[index] < range.from || data[index] > range.to
91
+            throw new IllegalArgumentException("Unexpected character: " + data[index]);
92
+		
93
+        return data[index++];
94
+    }
95
+
96
+    /**
97
+     * Returns the next character in the stream.
98
+     *
99
+     * @return the next character
100
+     */
101
+    public next() as char
102
+		=> index >= data.length ? 0 : data[index++];
103
+	
104
+    /**
105
+     * Checks if the end of the stream has been reached.
106
+     *
107
+     * @return true if there are more characters in the stream
108
+     */
109
+    public hasMore() as bool
110
+		=> index < data.length;
111
+}

+ 72
- 0
parser/src/lexer/CompiledDFA.zs View File

@@ -0,0 +1,72 @@
1
+public struct CompiledDFA<T : TokenType> {
2
+	public static createLexerDFA<T : TokenType : Comparable<T>>(tokenTypes as T[]) as CompiledDFA<T> {
3
+		val tokens = tokenTypes.filter(token => token.regexp != null);
4
+		val regexps = tokenTypes.filter(token => token.regexp != null).map(token => token.regexp);
5
+		return new NFA<T>(regexps, tokens).compile();
6
+	}
7
+	
8
+    val transitions as int[int][];
9
+    val finals as T?[];
10
+	
11
+    /**
12
+     * Constructs a compiled DFA from the specified transition graph and finals
13
+     * arrays.
14
+     *
15
+     * The transition array specifies all transitions for each state. The finals
16
+     * array specifies the final class index of each state, or NOFINAL if the state
17
+     * is not a final. There can multiple final classes, which can, for example,
18
+     * be used to distinguish token types.
19
+     *
20
+     * @param transitions transitions graph
21
+     * @param finals finals
22
+     */
23
+    public this(transitions as int[int][], finals as T?[]) {
24
+        this.transitions = transitions;
25
+        this.finals = finals;
26
+    }
27
+	
28
+	/**
29
+	 * Determines the token type for the given token. Returns null if the
30
+	 * given token is not a valid token.
31
+	 * 
32
+	 * @param value token value
33
+	 * @return token type
34
+	 */
35
+	public eval(value as string) as T? {
36
+		var state = 0;
37
+		
38
+		for c in value {
39
+			state = transitions[state][c];
40
+			if state == int.MIN_VALUE
41
+				return null;
42
+		}
43
+		
44
+		return finals[state];
45
+	}
46
+	
47
+	/**
48
+	 * Checks if this value evaluates to a valid token.
49
+	 * 
50
+	 * @param value value to check
51
+	 * @return true if matching, false otherwise
52
+	 */
53
+	public matches(value as string) as bool
54
+		=> eval(value) != null;
55
+
56
+    /* Used for debugging */
57
+	public implements StringBuildable {
58
+		toString(output as StringBuilder) as void {
59
+			for i, map in transitions {
60
+				for k, v in map {
61
+					output << "edge(" << i << ", " << k << "): " << v << "\r\n";
62
+				}
63
+			}
64
+			for i, f in finals {
65
+				if f == null
66
+					continue;
67
+				
68
+				output << "final(" << i << "): " << (f as string) << "\r\n";
69
+			}
70
+		}
71
+    }
72
+}

+ 167
- 0
parser/src/lexer/DFA.zs View File

@@ -0,0 +1,167 @@
1
+import collections.LinkedList;
2
+import collections.HashSet;
3
+
4
+public class DFA<T : TokenType> {
5
+    static val NOFINAL as int : get = int.MIN_VALUE;
6
+
7
+    val initial as DFAState<T>;
8
+
9
+    /**
10
+     * Constructs a new DFA with the specified initial state.
11
+     *
12
+     * @param initial initial state
13
+	 * @param tokenClass token class
14
+     */
15
+    public this(initial as DFAState<T>) {
16
+        this.initial = initial;
17
+    }
18
+
19
+    /**
20
+     * Compiles this DFA into a more efficient structure.
21
+     *
22
+     * @return the compiled DFA
23
+     */
24
+    public compile() as CompiledDFA<T> {
25
+        val nodeList = [] as DFAState<T>[];
26
+        val nodes = new int[DFAState<T>];
27
+		
28
+        nodes[initial] = 0;
29
+        nodeList.add(initial);
30
+		
31
+        /* Find all reachable nodes in the dfs */
32
+        var counter = 1;
33
+        val todo = new LinkedList<DFAState<T>>();
34
+        todo.add(initial);
35
+
36
+        while !todo.empty {
37
+            val current = todo.poll();
38
+
39
+			for k, next in current.transitions {
40
+                if next !in nodes {
41
+                    todo.add(next);
42
+                    nodes[next] = counter++;
43
+                    nodeList.add(next);
44
+                }
45
+            }
46
+        }
47
+
48
+        /* Compile */
49
+        val transitions = new int[int][](counter);
50
+		val finals2 = new T?[](counter);
51
+		
52
+		for node in nodeList {
53
+            val index = nodes[node];
54
+            finals2[index] = node.finalCode;
55
+			
56
+			for k, next in node.transitions
57
+                transitions[index][k] = nodes[next];
58
+        }
59
+
60
+        return new CompiledDFA<T>(transitions, finals2);
61
+    }
62
+
63
+    /**
64
+     * Generates the minimal version of this DFA.
65
+     *
66
+     * @return the minimal DFA
67
+     */
68
+    public optimize() as DFA<T> {
69
+        val compiled = compile();
70
+        val transitions = compiled.transitions;
71
+        val size = transitions.length;
72
+
73
+        /* Collect all edges and determine alphabet */
74
+        val alphabet = new HashSet<int>();
75
+		for transition in transitions {
76
+			for k, v in transition
77
+				alphabet.add(k);
78
+		}
79
+
80
+        /* Initialize distinguishing array */
81
+        var distinguishable = new bool[,](size + 1, size + 1, (i, j) => compiled.finals[i] != compiled.finals[j]);
82
+		for i in 0 .. size {
83
+			distinguishable[i, size] = true;
84
+			distinguishable[size, i] = true;
85
+		}
86
+
87
+        /* Minimization algorithm implementation */
88
+        var changed = false;
89
+        do {
90
+            changed = false;
91
+			for x in alphabet {
92
+				for i in 0 .. size {
93
+					val ti = transitions[i].getOrDefault(x, size);
94
+					for j in 0 .. size {
95
+						if distinguishable[i, j]
96
+							continue;
97
+						
98
+						val tj = transitions[j].getOrDefault(x, size);
99
+						if distinguishable[ti, tj] {
100
+							distinguishable[i, j] = true;
101
+							changed = true;
102
+						}
103
+					}
104
+				}
105
+			}
106
+        } while (changed);
107
+
108
+        /* Group nodes */
109
+		var nodeMap = new DFAState<T>[int];
110
+        for i in 0 .. size {
111
+            for j in 0 .. size {
112
+                if !distinguishable[i, j] && j in nodeMap {
113
+                    nodeMap[i] = nodeMap[j];
114
+                    if compiled.finals[i] != null
115
+							&& nodeMap[j].finalCode != null
116
+							&& nodeMap[j].finalCode != compiled.finals[i]
117
+						throw new Exception("Two conflicting finals detected");
118
+					
119
+                    continue i;
120
+                }
121
+            }
122
+			
123
+			var node = new DFAState<T>;
124
+            node.finalCode = compiled.finals[i];
125
+            nodeMap[i] = node;
126
+        }
127
+		
128
+		for i, transition in compiled.transitions {
129
+			for k, v in transition
130
+				nodeMap[i].addTransition(k, nodeMap[v]);
131
+		}
132
+        
133
+        return new DFA<T>(nodeMap[0]);
134
+    }
135
+	
136
+	public implements StringBuildable {
137
+		public toString(output as StringBuilder) as void {
138
+			val dfs = compile();
139
+			for i, map in dfs.transitions {
140
+				for k, v in map {
141
+					output << "edge(" << i << ", " << k << "): " << v << "\r\n";
142
+				}
143
+			}
144
+			for i, finalCode in dfs.finals {
145
+				if finalCode == null
146
+					continue;
147
+				
148
+				output << "final(" << i << ": " << finalCode as string << "\r\n";
149
+			}
150
+		}
151
+	}
152
+
153
+    // ============================
154
+    // === Public inner classes ===
155
+    // ============================
156
+
157
+    /**
158
+     * Represents a state in a DFA.
159
+     */
160
+    public static class DFAState<T> {
161
+        val transitions as DFAState<T>[int] = new DFAState<T>[int];
162
+        var finalCode as T? : get, set = null;
163
+		
164
+        public addTransition(label as int, next as DFAState<T>) as void
165
+            => transitions[label] = next;
166
+    }
167
+}

+ 555
- 0
parser/src/lexer/NFA.zs View File

@@ -0,0 +1,555 @@
1
+import collections.HashSet;
2
+import collections.LinkedList;
3
+
4
+public class NFA<T : Comparable<T>> {
5
+    static val EPSILON as int : get = int.MIN_VALUE + 1;
6
+
7
+    val initial as NFAState<T>;
8
+    val converted as DFA.DFAState<T>[NodeSet];
9
+
10
+    /**
11
+     * Creates a new NFA with the specified initial state.
12
+     *
13
+	 * @param tokenClass token class
14
+     * @param initial initial state
15
+     */
16
+    public this(initial as NFAState<T>) {
17
+        this.initial = initial;
18
+    }
19
+
20
+    /**
21
+     * Creates a new NFA from the specified regular expression.
22
+     *
23
+     * Note: the regular expression implementation is not complete. Shorthand
24
+     * character classes (\s, \w, \d), hexadecimal and unicode escape sequences and
25
+     * unicode properties are not implemented.
26
+     * 
27
+     * Anchors, lazy plus operators, lookbehind and lookforward are not implemented
28
+     * since they cannot be implemented in an NFA.
29
+     *
30
+     * @param regexp regular expression
31
+	 * @param state resulting state
32
+     */
33
+    public this(regexp as string, state as T) {
34
+        val main = processRegExp(new CharStream(regexp));
35
+        initial = new NFAState<T>();
36
+        initial.addTransition(main.tailLabel, main.tail);
37
+        main.head.finalCode = state;
38
+    }
39
+
40
+    /**
41
+     * Converts an array of regular expressions to an NFA. Each regular expression
42
+     * can have its own final class. The length of both arrays must match.
43
+     *
44
+     * @param regexp regular expression array
45
+     * @param finals final classes
46
+     */
47
+    public this(regexps as string?[], finals as T[]) {
48
+        initial = new NFAState<T>();
49
+		
50
+        for i, regexp in regexps {
51
+			if regexp == null
52
+				continue;
53
+			
54
+			try {
55
+				var partial = processRegExp(new CharStream(regexp));
56
+				partial.head.finalCode = finals[i];
57
+				initial.addTransition(partial.tailLabel, partial.tail);
58
+			} catch ex as IllegalArgumentException {
59
+				throw new Exception("Error parsing " + regexp[i], ex);
60
+			}
61
+        }
62
+    }
63
+
64
+    /**
65
+     * Converts this NFA to a DFA. The resulting DFA is not optimal. If the
66
+     * NFA is ambiguous, the token with lowest value will receive preference.
67
+     *
68
+     * @return this NFA as DFA
69
+     */
70
+    public toDFA() as DFA<T> {
71
+        converted = {};
72
+        val closure = new HashSet<NFAState<T>>;
73
+        this.initial.closure(closure);
74
+        val init = convert(new NodeSet(closure));
75
+        return new DFA<T>(init);
76
+    }
77
+	
78
+	/**
79
+	 * Converts the NFA to a DFA and optimizes and compiles it into an efficient
80
+	 * format.
81
+	 * 
82
+	 * @return compiled DFA
83
+	 */
84
+	public compile() as CompiledDFA<T>
85
+		=> toDFA().optimize().compile();
86
+
87
+    // =======================
88
+    // === Private methods ===
89
+    // =======================
90
+
91
+    /* Converts a set of possible states to a DFA state. */
92
+    private convert(nodes as NodeSet) as DFA.DFAState<T> {
93
+		if nodes in converted
94
+			return converted[nodes];
95
+		
96
+		val node = new DFA.DFAState<T>();
97
+		converted[nodes] = node;
98
+
99
+		val edgeSet = new HashSet<int>;
100
+		for n in nodes.nodes
101
+			n.alphabet(edgeSet);
102
+		
103
+		for i in edgeSet {
104
+			val edge = new HashSet<NFAState<T>>();
105
+			for n in nodes.nodes
106
+				n.edge(i, edge);
107
+			
108
+			val nodeSet = new NodeSet(edge);
109
+			node.addTransition(i, convert(nodeSet));
110
+		}
111
+		
112
+		var finalCode as T? = null;
113
+		for n in nodes.nodes {
114
+			if n.state != null {
115
+				if finalCode != null {
116
+					finalCode = n.state.compareTo(finalCode) < 0 ? n.state : finalCode;
117
+				} else {
118
+					finalCode = n.state;
119
+				}
120
+			}
121
+		}
122
+		
123
+		node.finalCode = finalCode;
124
+		return node;
125
+    }
126
+
127
+    /* Processes a regular expression */
128
+    private processRegExp(stream as CharStream) as Partial<T> {
129
+        val partial = processRegExp0(stream);
130
+        if stream.optional('|') {
131
+            val partials = new Partial<T>[];
132
+            partials.add(partial);
133
+            partials.add(processRegExp0(stream));
134
+            while stream.optional('|')
135
+                partials.add(processRegExp0(stream));
136
+			
137
+            val head = new NFAState<T>();
138
+            val tail = new NFAState<T>();
139
+            for p in partials {
140
+                tail.addTransition(p.tailLabel, p.tail);
141
+                p.head.addTransition(EPSILON, head);
142
+            }
143
+            return new Partial<T>(EPSILON, tail, head);
144
+        } else {
145
+            return partial;
146
+        }
147
+    }
148
+
149
+    /* Processes an element of an alternation clause */
150
+    private processRegExp0(stream as CharStream) as Partial<T>
151
+	{
152
+        var partial = processRegExp1(stream);
153
+        while !stream.peek(')') && !stream.peek('|') && stream.hasMore() {
154
+            val partial2 = processRegExp1(stream);
155
+            partial.head.addTransition(partial2.tailLabel, partial2.tail);
156
+            partial = new Partial<T>(partial.tailLabel, partial.tail, partial2.head);
157
+        }
158
+        return partial;
159
+    }
160
+
161
+    /* Processes a partial with optional repetition */
162
+    private processRegExp1(stream as CharStream) as Partial<T>
163
+	{
164
+        val partial = processPartial(stream);
165
+        if stream.optional('*') {
166
+            val node = new NFAState<T>();
167
+            partial.head.addTransition(EPSILON, node);
168
+            node.addTransition(partial.tailLabel, partial.tail);
169
+            return new Partial<T>(EPSILON, node, node);
170
+        } else if stream.optional('+') {
171
+            val node = new NFAState<T>();
172
+            partial.head.addTransition(EPSILON, node);
173
+            node.addTransition(partial.tailLabel, partial.tail);
174
+            return new Partial<T>(partial.tailLabel, partial.tail, node);
175
+        } else if stream.optional('?') {
176
+            val node = new NFAState<T>();
177
+            node.addTransition(EPSILON, partial.head);
178
+            node.addTransition(partial.tailLabel, partial.tail);
179
+            return new Partial<T>(EPSILON, node, partial.head);
180
+        } else if stream.optional('{') {
181
+            val amount = processInt(stream);
182
+            if amount < 0
183
+				throw new IllegalArgumentException("Repitition count expected");
184
+			
185
+            if stream.optional(',') {
186
+                val amount2 = processInt(stream);
187
+                stream.required('}');
188
+                if amount2 < 0 {
189
+                    // unbounded
190
+                    val duplicates = new Partial<T>[](amount, i => i == amount - 1 ? partial : partial.duplicate());
191
+					for i in 0 .. duplicates.length - 1
192
+                        duplicates[i].head.addTransition(duplicates[i + 1].tailLabel, duplicates[i + 1].tail);
193
+					
194
+                    duplicates[amount - 1].head.addTransition(duplicates[amount - 1].tailLabel, duplicates[amount - 1].tail);
195
+                    return new Partial<T>(duplicates[0].tailLabel, duplicates[0].tail, duplicates[amount - 1].head);
196
+                } else {
197
+                    val duplicates = new Partial<T>[](amount2, i => i == amount2 - 1 ? partial : partial.duplicate());
198
+                    for i in 0 .. duplicates.length - 1
199
+                        duplicates[i].head.addTransition(duplicates[i + 1].tailLabel, duplicates[i + 1].tail);
200
+					
201
+                    for i in amount .. amount2 {
202
+                        if (i == 0) {
203
+                            /* insert additional node before the chain because minimal repeat is 0 */
204
+                            val additional = new NFAState<T>();
205
+                            additional.addTransition(duplicates[0].tailLabel, duplicates[0].tail);
206
+                            additional.addTransition(EPSILON, duplicates[amount2 - 1].head);
207
+                            duplicates[0].tailLabel = EPSILON;
208
+                            duplicates[0].tail = additional;
209
+                        } else {
210
+                            duplicates[i - 1].head.addTransition(EPSILON, duplicates[amount2 - 1].head);
211
+                        }
212
+                    }
213
+                    return new Partial<T>(duplicates[0].tailLabel, duplicates[0].tail, duplicates[amount2 - 1].head);
214
+                }
215
+            } else {
216
+                stream.required('}');
217
+                
218
+                val duplicates = new Partial<T>[](amount, i => i == amount - 1 ? partial : partial.duplicate());
219
+				for i in 0 .. duplicates.length - 1
220
+                    duplicates[i].head.addTransition(duplicates[i + 1].tailLabel, duplicates[i + 1].tail);
221
+				
222
+                return new Partial<T>(duplicates[0].tailLabel, duplicates[0].tail, duplicates[amount - 1].head);
223
+            }
224
+        } else {
225
+            return partial;
226
+        }
227
+    }
228
+
229
+    /* Processes a part of a regular expression, which can be a character,
230
+     * expression between brackets, a dot or a character list */
231
+    private processPartial(stream as CharStream) as Partial<T> {
232
+        if stream.optional('(') {
233
+            val result = processRegExp(stream);
234
+            stream.required(')');
235
+            return result;
236
+        } else if stream.optional('[') {
237
+            val head = new NFAState<T>();
238
+            val tail = new NFAState<T>();
239
+			
240
+			for i in processCharList(stream)
241
+				tail.addTransition(i, head);
242
+			
243
+            stream.required(']');
244
+            return new Partial<T>(EPSILON, tail, head);
245
+        } else if (stream.optional('.')) {
246
+            val head = new NFAState<T>();
247
+            val tail = new NFAState<T>();
248
+			
249
+			for i in 0 .. 257 // 256 means 'any other unicode character'
250
+				tail.addTransition(i, head);
251
+			
252
+            return new Partial<T>(EPSILON, tail, head);
253
+        } else {
254
+            val node = new NFAState<T>();
255
+            return new Partial<T>(processChar(stream), node, node);
256
+        }
257
+    }
258
+
259
+    /* Processes a character list */
260
+    private processCharList(stream as CharStream) as HashSet<int>
261
+	{
262
+        val invert = stream.optional('^');
263
+        val base = new HashSet<int>;
264
+        do {
265
+            processCharPartial(base, stream);
266
+        } while !stream.peek(']');
267
+		
268
+        if invert {
269
+            val result = new HashSet<int>();
270
+			for i in 0 .. 257 {
271
+				if i !in base
272
+					result.add(i);
273
+            }
274
+            return result;
275
+        } else {
276
+            return base;
277
+        }
278
+    }
279
+
280
+    /* Processes a character partial, which can be a single character or a range
281
+     * of characters. */
282
+    private processCharPartial(out as HashSet<int>, stream as CharStream) as void
283
+	{
284
+        if stream.optional('.') {
285
+			for i in 0 .. 257
286
+				out.add(i);
287
+        } else {
288
+			val from = processChar(stream);
289
+            if stream.optional('-') {
290
+                val to = processChar(stream);
291
+				for i in from .. to
292
+                    out.add(i);
293
+            } else {
294
+                out.add(from);
295
+            }
296
+        }
297
+    }
298
+
299
+    /* Processes a single character */
300
+    private processChar(stream as CharStream) as int
301
+	{
302
+        if (stream.optional('\\')) { //'
303
+			val c = stream.next();
304
+			switch (c) {
305
+				case 'e': return -1;
306
+				case 'r': return '\r';
307
+				case 'n': return '\n';
308
+				case 't': return '\t';
309
+				case '[': return '[';
310
+				case ']': return ']';
311
+				case '(': return '(';
312
+				case ')': return ')';
313
+				case '.': return '.';
314
+				case '+': return '+';
315
+				case '-': return '-';
316
+				case '\\': return '\\';//'
317
+				case '{': return '{';
318
+				case '}': return '}';
319
+				case '?': return '?';
320
+				case '*': return '*';
321
+				case '~': return '~';
322
+				case '|': return '|';
323
+				case '^': return '^';
324
+				default:
325
+		            throw new IllegalArgumentException("Invalid character: " + (c as char));
326
+			}
327
+        } else {
328
+			val c = stream.next();
329
+			switch (c) {
330
+				case '[':
331
+				case ']':
332
+				case '(':
333
+				case ')':
334
+				case '{':
335
+				case '}':
336
+				case '.':
337
+				case '?':
338
+				case '*':
339
+	                throw new IllegalArgumentException("Invalid character: " + (c as char));
340
+				default:
341
+					return c;
342
+			}
343
+        }
344
+    }
345
+
346
+    /* Processes an optional integer, returns -1 if the next character do not
347
+     * represent an integer */
348
+    private processInt(stream as CharStream) as int
349
+	{
350
+        var data = stream.optional('0'..'9') - '0';
351
+        if data < 0
352
+			return -1;
353
+		
354
+        var ch = stream.optional('0'..'9');
355
+        while ch != 0 {
356
+            data = data * 10 + (ch - '0');
357
+            ch = stream.optional('0'..'9');
358
+        }
359
+        return data;
360
+    }
361
+
362
+    // ============================
363
+    // === Public inner classes ===
364
+    // ============================
365
+
366
+    /**
367
+     * Represents an NFA state.
368
+	 * 
369
+	 * @param <T> final type
370
+     */
371
+    public static class NFAState<T>
372
+	{
373
+        static var counter as int = 1;
374
+
375
+        val transitions as Transition[] = [];
376
+		var closure as NFAState<T>[]? = null;
377
+		val index as int = counter++;
378
+		val state as T?;
379
+		
380
+        /**
381
+         * Adds a transition.
382
+         *
383
+         * @param label transition label
384
+         * @param next next state
385
+         */
386
+        public addTransition(label as int, next as NFAState<T>) as void
387
+			=> transitions.add({ label, next });
388
+		
389
+        /**
390
+         * Sets the final state of this state.
391
+         *
392
+         * @param finalCode final code
393
+         */
394
+        public set finalCode as T 
395
+		{
396
+            if this.state == $
397
+				return;
398
+			
399
+            this.state = $;
400
+
401
+            for t in transitions
402
+                if t.label == EPSILON
403
+                    t.next.finalCode = $;
404
+        }
405
+
406
+        // =======================
407
+        // === Private methods ===
408
+        // =======================
409
+
410
+        /**
411
+         * Determines the (partial) closure of this state.
412
+         *
413
+         * @param output output to store the closure in
414
+         */
415
+        private closure(output as HashSet<NFAState<T>>) as void {
416
+            for node in closure()
417
+				output.add(node);
418
+        }
419
+
420
+        /**
421
+         * Calculates the (full) closure of this state.
422
+         *
423
+         * @return this state's closure
424
+         */
425
+        private closure() as NFAState<T>[] {
426
+            if closure == null {
427
+                closure = [];
428
+                val tmp = new HashSet<NFAState<T>>();
429
+                tmp.add(this);
430
+                for transition in transitions {
431
+                    if transition.label == EPSILON {
432
+                        if transition.next !in tmp {
433
+                            tmp.add(transition.next);
434
+                            transition.next.closure(tmp);
435
+                        }
436
+                    }
437
+                }
438
+                for node in tmp
439
+					closure.add(node);
440
+            }
441
+			
442
+            return closure;
443
+        }
444
+
445
+        /**
446
+         * Calculates the possible set of states after a transition with the
447
+         * specified value
448
+         *
449
+         * @param label transition label
450
+         * @param output possible set of states (out)
451
+         */
452
+        private edge(label as int, output as HashSet<NFAState<T>>) as void {
453
+            for transition in transitions {
454
+                if transition.label == label {
455
+                    if transition.next !in output {
456
+                        output.add(transition.next);
457
+                        transition.next.closure(output);
458
+                    }
459
+                }
460
+            }
461
+        }
462
+
463
+        /**
464
+         * Calculates the alphabet of this state.
465
+         *
466
+         * @param output alphabet (out)
467
+         */
468
+        private alphabet(output as HashSet<int>) as void
469
+		{
470
+            for node in closure() {
471
+                for t in node.transitions {
472
+                    if t.label != EPSILON
473
+                        output.add(t.label);
474
+                }
475
+            }
476
+        }
477
+
478
+        // ===================
479
+        // === Inner class ===
480
+        // ===================
481
+		
482
+        private struct Transition {
483
+            var label as int;
484
+			val next as NFAState<T>;
485
+        }
486
+    }
487
+
488
+    /* Hashable set of nodes */
489
+    private class NodeSet
490
+	{
491
+        val nodes as NFAState<T>[];
492
+		
493
+        public this(nodes as HashSet<NFAState<T>>)
494
+		{
495
+            this.nodes = new NFAState<T>[](nodes.size);
496
+			var i = 0;
497
+            for node in nodes
498
+				this.nodes[i++] = node;
499
+			
500
+            this.nodes.sort((o1, o2) => o1.index - o2.index);
501
+        }
502
+		
503
+		public implements Hashable<NodeSet> {
504
+			hashCode() as int
505
+				=> nodes.objectHashCode;
506
+			
507
+			==(other as NodeSet) as bool
508
+				=> nodes === other.nodes;
509
+		}
510
+    }
511
+
512
+    /**
513
+     * Contains a partially parsed regular expression. Has a head and a tail.
514
+     *
515
+     * The tail is a transition and thus has a label and a next node. The head
516
+     * is the 'final' state of the partial.
517
+     */
518
+    private static struct Partial<T>
519
+	{
520
+        var tailLabel as int;
521
+        var tail as NFAState<T>;
522
+        var head as NFAState<T>;
523
+		
524
+		public this(tailLabel as int, tail as NFAState<T>, head as NFAState<T>) {
525
+			this.tailLabel = tailLabel;
526
+			this.tail = tail;
527
+			this.head = head;
528
+		}
529
+		
530
+        /**
531
+         * Duplicates the NFA of this partial.
532
+         *
533
+         * @return a duplicate of this partial
534
+         */
535
+        public duplicate() as Partial<T>
536
+		{
537
+			val nodes = new NFAState<T>[NFAState<T>];
538
+			val todo = new LinkedList<NFAState<T>>;
539
+            todo.add(tail);
540
+            nodes[tail] = new NFAState<T>;
541
+            while !todo.empty {
542
+                val node = todo.poll();
543
+                for t in node.transitions {
544
+                    if t.next !in nodes {
545
+                        nodes[t.next] = new NFAState<T>;
546
+                        todo.add(t.next);
547
+                    }
548
+                    nodes[node].addTransition(t.label, nodes[t.next]);
549
+                }
550
+            }
551
+
552
+            return new Partial<T>(tailLabel, nodes[tail], nodes[head]);
553
+        }
554
+    }
555
+}

+ 8
- 0
parser/src/lexer/Token.zs View File

@@ -0,0 +1,8 @@
1
+import shared.CodePosition;
2
+
3
+export interface Token<TT : TokenType> {
4
+	get position as CodePosition;
5
+	get type as TT;
6
+	get content as string;
7
+	get whitespaceBefore as string;
8
+}

+ 314
- 0
parser/src/lexer/TokenStream.zs View File

@@ -0,0 +1,314 @@
1
+import collections.LinkedList;
2
+import collections.Stack;
3
+import io.Reader;
4
+import io.StringReader;
5
+import shared.CodePosition;
6
+
7
+/**
8
+ * Represents a token stream. A token stream reads characters from a reader and
9
+ * presents it as a series of tokens. Can be used to implement LL(*) parsers.
10
+ *
11
+ * Token classes with a negative class are considered to be whitespace.
12
+ *
13
+ * @author Stan Hebben
14
+ * @param <T> token class
15
+ * @param <TT> token type class
16
+ */
17
+export abstract class TokenStream<T : Token<TT>, TT : TokenType> {
18
+	val filename as string : get;
19
+    val reader as CountingReader;
20
+    val dfa as CompiledDFA<TT>;
21
+    val tokenMemory as LinkedList<T>;
22
+    val marks as Stack<int>;
23
+	val eof as TT;
24
+	val tabSize as int = 4;
25
+	
26
+    val next as T;
27
+	val nextChar as int;
28
+	val line as int : get;
29
+	val lineOffset as int : get;
30
+	
31
+    val tokenMemoryOffset as int;
32
+    val tokenMemoryCurrent as int;
33
+    
34
+    /**
35
+     * Creates a token stream using the specified reader and DFA.
36
+     *
37
+	 * @param filename filename
38
+     * @param reader reader to read characters from
39
+     * @param dfa DFA to tokenize the stream
40
+	 * @param eof end of file token type
41
+     */
42
+	public this(filename as string, reader as Reader, dfa as CompiledDFA<TT>, eof as TT) {
43
+		if eof.isWhitespace // important for the advance() method
44
+			throw new IllegalArgumentException("EOF cannot be whitespace");
45
+		
46
+        tokenMemoryOffset = 0;
47
+        tokenMemoryCurrent = 0;
48
+        tokenMemory = new LinkedList<T>();
49
+        marks = new Stack<int>();
50
+        
51
+        this.reader = new CountingReader(reader);
52
+        this.dfa = dfa;
53
+		this.filename = filename;
54
+		this.eof = eof;
55
+		
56
+		nextChar = this.reader.read();
57
+		
58
+        line = 1;
59
+        lineOffset = 1;
60
+        advance();
61
+    }
62
+
63
+    /**
64
+     * Creates a token stream which reads data from the specified string.
65
+     *
66
+	 * @param filename filename
67
+     * @param data data to read
68
+     * @param dfa DFA to tokenize the stream
69
+	 * @param eof end of file token type
70
+     */
71
+	public this(filename as string, data as string, dfa as CompiledDFA<TT>, eof as TT) {
72
+        this(filename, new StringReader(data), dfa, eof);
73
+    }
74
+    
75
+    public peek() as T {
76
+        if tokenMemoryCurrent < tokenMemoryOffset + tokenMemory.size {
77
+            return tokenMemory[tokenMemoryCurrent - tokenMemoryOffset];
78
+        } else {
79
+            return next;
80
+        }
81
+    }
82
+
83
+    public isNext(type as TT) as bool
84
+		=> peek().type == type;
85
+	
86
+    public optional(type as TT) as T?
87
+		=> peek().type == type ? next() : null;
88
+	
89
+    public required(type as TT, error as string) as T {
90
+		val t = peek();
91
+        if t.type == type {
92
+            return next();
93
+        } else {
94
+			return requiredTokenNotFound(position, error, t);
95
+        }
96
+    }
97
+	
98
+	public get position as CodePosition
99
+		=> peek().position;
100
+	
101
+	// =====================
102
+    // === LL(*) ability ===
103
+	// =====================
104
+
105
+    /**
106
+     * Pushes a mark on the mark stack.
107
+     */
108
+    public pushMark() as void {
109
+        marks.push(tokenMemoryCurrent);
110
+    }
111
+
112
+    /**
113
+     * Pops a mark from the mark stack without reset.
114
+     */
115
+    public popMark() as void {
116
+        marks.pop();
117
+
118
+        if marks.empty {
119
+            tokenMemoryOffset = tokenMemoryCurrent;
120
+            tokenMemory.clear();
121
+        }
122
+    }
123
+
124
+    /**
125
+     * Pops a mark from the mark stack and resets the stream's position to it
126
+     */
127
+    public reset() as void {
128
+        tokenMemoryCurrent = marks.pop();
129
+    }
130
+	
131
+	/**
132
+	 * Replaces the current token with another one. Used to split composite tokens.
133
+	 * 
134
+	 * @param other 
135
+	 */
136
+	public replace(other as TT) as void {
137
+		next = createToken(next.position, next.whitespaceBefore, next.content, other);
138
+	}
139
+
140
+    public get hasMore as bool
141
+		=> next.type === eof;
142
+	
143
+    public next() as T {
144
+        if (tokenMemoryCurrent < tokenMemoryOffset + tokenMemory.size) {
145
+            return tokenMemory[(tokenMemoryCurrent++) - tokenMemoryOffset];
146
+        } else {
147
+            val result = next;
148
+            if marks.empty {
149
+                tokenMemoryOffset++;
150
+            } else {
151
+                tokenMemory.add(result);
152
+            }
153
+            tokenMemoryCurrent++;
154
+            advance();
155
+            return result;
156
+        }
157
+    }
158
+
159
+    // ==================================
160
+    // === Protected abstract methods ===
161
+    // ==================================
162
+	
163
+	/**
164
+	 * Called to create a token. May also be used to postprocess tokens while
165
+	 * generating them.
166
+	 * 
167
+	 * @param position token position (range)
168
+	 * @param value token value
169
+	 * @param tokenType token type
170
+	 * @return newly created token
171
+	 */
172
+	protected abstract createToken(
173
+			position as CodePosition,
174
+			whitespaceBefore as string,
175
+			value as string,
176
+			tokenType as TT) as T;
177
+	
178
+	/**
179
+	 * Called when a required token could not be found. Should log an error or
180
+	 * throw an exception. If no exception is thrown, the calling required
181
+	 * method will return the returned value as token value.
182
+	 * 
183
+	 * @param position erroring position
184
+	 * @param error error to be logged
185
+	 * @param token incorrect token
186
+	 */
187
+	protected abstract requiredTokenNotFound(
188
+			position as CodePosition,
189
+			error as string,
190
+			token as T) as T;
191
+	
192
+	/**
193
+	 * Called when the input contains an invalid token. Should either create
194
+	 * a token indicating an invalid token, or throw an exception.
195
+	 * 
196
+	 * @param position erroring position
197
+	 * @param token token value
198
+	 * @return a token marking an invalid token
199
+	 */
200
+	protected abstract invalidToken(
201
+			position as CodePosition,
202
+			whitespaceBefore as string,
203
+			token as string) as T;
204
+	
205
+    // =======================
206
+    // === Private methods ===
207
+    // =======================
208
+
209
+    /**
210
+     * Advances to the next non - whitespace token.
211
+     */
212
+    private advance() as void {
213
+		val whitespace = new StringBuilder();
214
+        while true {
215
+            advanceToken(whitespace);
216
+			if next.type.isWhitespace
217
+				whitespace << next.content;
218
+			else
219
+				break;
220
+        }
221
+    }
222
+
223
+    /**
224
+     * Advances to the next token.
225
+     */
226
+    private advanceToken(whitespace as string) as void {
227
+        if nextChar < 0 {
228
+			val position = new CodePosition(
229
+					filename,
230
+					line,
231
+					lineOffset,
232
+					line,
233
+					lineOffset);
234
+			
235
+            next = createToken(position, whitespace, "", eof);
236
+            return;
237
+        }
238
+		
239
+		var state = 0;
240
+		val value = new StringBuilder();
241
+		val fromLine = line;
242
+		val fromLineOffset = lineOffset;
243
+		while nextChar in dfa.transitions[state] {
244
+			value << nextChar as char;
245
+			state = dfa.transitions[state][nextChar];
246
+			line = reader.line;
247
+			lineOffset = reader.lineOffset;
248
+			nextChar = reader.read();
249
+		}
250
+		
251
+		if dfa.finals[state] != null {
252
+			if state == 0 {
253
+				value << nextChar as char;
254
+				next = invalidToken(new CodePosition(filename, fromLine, fromLineOffset, line, lineOffset),
255
+						whitespace,
256
+						value);
257
+				nextChar = reader.read();
258
+			}
259
+			
260
+			next = createToken(new CodePosition(filename, fromLine, fromLineOffset, line, lineOffset),
261
+					whitespace,
262
+					value,
263
+					dfa.finals[state]);
264
+		} else {
265
+			if nextChar < 0 && value.empty
266
+				return; // happens on comments at the end of files
267
+			
268
+			value << nextChar as char;
269
+			next = invalidToken(new CodePosition(filename, fromLine, fromLineOffset, line, lineOffset),
270
+					whitespace,
271
+					value);
272
+			nextChar = reader.read();
273
+		}
274
+    }
275
+
276
+    // =============================
277
+    // === Private inner classes ===
278
+    // =============================
279
+
280
+    /**
281
+     * Keeps a line and line offset count.
282
+     */
283
+    private static class CountingReader
284
+	{
285
+		var line as int;
286
+		var lineOffset as int;
287
+		val reader as Reader;
288
+		var eof as bool;
289
+		
290
+		public this(reader as Reader) {
291
+			this.reader = reader;
292
+			line = 1;
293
+			lineOffset = 1;
294
+		}
295
+
296
+        public read() as int
297
+		{
298
+            val ch = reader.read();
299
+            if ch == -1 {
300
+                eof = true;
301
+                return ch;
302
+            }
303
+            if ch == '\n' {
304
+                line++;
305
+                lineOffset = 1;
306
+            } else if ch == '\t' {
307
+				lineOffset += tabSize;
308
+			} else {
309
+                lineOffset++;
310
+            }
311
+            return ch;
312
+        }
313
+    }
314
+}

+ 5
- 0
parser/src/lexer/TokenType.zs View File

@@ -0,0 +1,5 @@
1
+export interface TokenType {
2
+	get regexp as string?;
3
+	get isWhitespace as bool;
4
+	as string;
5
+}

+ 17
- 0
parser/src/lexer/ZSToken.zs View File

@@ -0,0 +1,17 @@
1
+import shared.CodePosition;
2
+
3
+export class ZSToken {
4
+	val position as CodePosition : public get;
5
+	val type as ZSTokenType : public get;
6
+	val content as string : public get;
7
+	val whitespaceBefore as string : public get;
8
+	
9
+	public this(position as CodePosition, type as ZSTokenType, content as string, whitespaceBefore as string) {
10
+		this.position = position;
11
+		this.type = type;
12
+		this.content = content;
13
+		this.whitespaceBefore = whitespaceBefore;
14
+	}
15
+	
16
+	public implements Token<ZSTokenType> {}
17
+}

+ 160
- 0
parser/src/lexer/ZSTokenStream.zs View File

@@ -0,0 +1,160 @@
1
+import io.Reader;
2
+import codemodel.WhitespaceInfo;
3
+import shared.CompileException;
4
+import shared.CompileExceptionCode;
5
+import shared.CodePosition;
6
+
7
+/**
8
+ *
9
+ * @author Stan Hebben
10
+ */
11
+public class ZSTokenStream : TokenStream<ZSToken, ZSTokenType> {
12
+	private static val DFA as CompiledDFA<ZSTokenType> = CompiledDFA.createLexerDFA<ZSTokenType>(ZSTokenType.VALUES);
13
+	private static val KEYWORDS as ZSTokenType[string];
14
+	
15
+	static {
16
+		KEYWORDS = {
17
+			"import": K_IMPORT,
18
+			"alias": K_ALIAS,
19
+			"class": K_CLASS,
20
+			"interface": K_INTERFACE,
21
+			"enum": K_ENUM,
22
+			"struct": K_STRUCT,
23
+			"expand": K_EXPAND,
24
+			"function": K_FUNCTION,
25
+			"variant": K_VARIANT,
26
+		
27
+			"abstract": K_ABSTRACT,
28
+			"final": K_FINAL,
29
+			"override": K_OVERRIDE,
30
+			"const": K_CONST,
31
+			"private": K_PRIVATE,
32
+			"public": K_PUBLIC,
33
+			"export": K_EXPORT,
34
+			"static": K_STATIC,
35
+			"protected": K_PROTECTED,
36
+			"implicit": K_IMPLICIT,
37
+			"virtual": K_VIRTUAL,
38
+			"extern": K_EXTERN,
39
+		
40
+			"val": K_VAL,
41
+			"var": K_VAR,
42
+			"get": K_GET,
43
+			"implements": K_IMPLEMENTS,
44
+			"set": K_SET,
45
+		
46
+			"void": K_VOID,
47
+			"any": K_ANY,
48
+			"bool": K_BOOL,
49
+			"byte": K_BYTE,
50
+			"sbyte": K_SBYTE,
51
+			"short": K_SHORT,
52
+			"ushort": K_USHORT,
53
+			"int": K_INT,
54
+			"uint": K_UINT,
55
+			"long": K_LONG,
56
+			"ulong": K_ULONG,
57
+			"float": K_FLOAT,
58
+			"double": K_DOUBLE,
59
+			"char": K_CHAR,
60
+			"string": K_STRING,
61
+		
62
+			"if": K_IF,
63
+			"else": K_ELSE,
64
+			"do": K_DO,
65
+			"while": K_WHILE,
66
+			"for": K_FOR,
67
+			"throw": K_THROW,
68
+			"lock": K_LOCK,
69
+			"try": K_TRY,
70
+			"catch": K_CATCH,
71
+			"finally": K_FINALLY,
72
+			"return": K_RETURN,
73
+			"break": K_BREAK,
74
+			"continue": K_CONTINUE,
75
+			"switch": K_SWITCH,
76
+			"case": K_CASE,
77
+			"default": K_DEFAULT,
78
+		
79
+			"in": K_IN,
80
+			"is": K_IS,
81
+			"as": K_AS,
82
+			"match": K_MATCH,
83
+			"throws": K_THROWS,
84
+		
85
+			"this": K_THIS,
86
+			"super": K_SUPER,
87
+			"null": K_NULL,
88
+			"true": K_TRUE,
89
+			"false": K_FALSE,
90
+			"new": K_NEW
91
+		};
92
+	}
93
+	
94
+	var whitespaceBuffer as string? = null;
95
+	
96
+	public this(filename as string, reader as Reader) {
97
+		super(filename, reader, DFA, EOF);
98
+	}
99
+	
100
+	public loadWhitespace() as string {
101
+		if whitespaceBuffer == null
102
+			whitespaceBuffer = peek().whitespaceBefore;
103
+		
104
+		return whitespaceBuffer;
105
+	}
106
+	
107
+	public reloadWhitespace() as void {
108
+		whitespaceBuffer = peek().whitespaceBefore;
109
+	}
110
+	
111
+	public grabWhitespace() as string {
112
+		val result = loadWhitespace();
113
+		whitespaceBuffer = null;
114
+		return result;
115
+	}
116
+	
117
+	public grabWhitespaceLine() as string {
118
+		val whitespace = loadWhitespace();
119
+		if '\n' in whitespace {
120
+			var index = whitespace.indexOf('\n');
121
+			whitespaceBuffer = whitespace[(index + 1) .. $];
122
+			return whitespace[0 .. index];
123
+		} else {
124
+			whitespaceBuffer = "";
125
+			return whitespace;
126
+		}
127
+	}
128
+	
129
+	public skipWhitespaceNewline() as void {
130
+		loadWhitespace();
131
+		val index = whitespaceBuffer.indexOf('\n');
132
+		if index >= 0
133
+			whitespaceBuffer = whitespaceBuffer[(index + 1) .. $];
134
+	}
135
+	
136
+	public collectWhitespaceInfoForBlock(whitespace as string) as WhitespaceInfo
137
+		=> WhitespaceInfo.from(whitespace, "", false);
138
+	
139
+	public collectWhitespaceInfo(whitespace as string, skipLineBefore as bool) as WhitespaceInfo
140
+		=> WhitespaceInfo.from(whitespace, grabWhitespaceLine(), skipLineBefore);
141
+	
142
+	override createToken(
143
+		position as CodePosition,
144
+		whitespaceBefore as string,
145
+		value as string,
146
+		tokenType as ZSTokenType) as ZSToken
147
+	{
148
+		if tokenType == T_IDENTIFIER && value in KEYWORDS
149
+			tokenType = KEYWORDS[value];
150
+		
151
+		return new ZSToken(position, tokenType, value, whitespaceBefore);
152
+	}
153
+
154
+	override requiredTokenNotFound(position as CodePosition, error as string, token as ZSToken) as void {
155
+		throw new CompileException(position, UNEXPECTED_TOKEN, error);
156
+	}
157
+
158
+	override invalidToken(position as CodePosition, whitespaceBefore as string, token as string) as ZSToken
159
+		=> new ZSToken(position, INVALID, token, whitespaceBefore);
160
+}

+ 164
- 0
parser/src/lexer/ZSTokenType.zs View File

@@ -0,0 +1,164 @@
1
+export enum ZSTokenType {
2
+	T_COMMENT_SCRIPT("#[^\n]*[\n\\e]", true),
3
+	T_COMMENT_SINGLELINE("//[^\n]*[\n\\e]", true),
4
+	T_COMMENT_MULTILINE("/\\*([^\\*]|(\\*+([^\\*/])))*\\*+/", true),
5
+	T_WHITESPACE("[ \t\r\n]*", true),
6
+	T_IDENTIFIER("[a-zA-Z_][a-zA-Z_0-9]*"),
7
+	T_FLOAT("\\-?(0|[1-9][0-9]*)\\.[0-9]+([eE][\\+\\-]?[0-9]+)?"),
8
+	T_INT("\\-?(0|[1-9][0-9]*)"),
9
+	T_STRING_SQ("\"([^\"\\\\]|\\\\([\'\"\\\\/bfnrt&]|u[0-9a-fA-F]{4}))*\""), //"
10
+	T_STRING_DQ("\'([^\'\\\\]|\\\\([\'\"\\\\/bfnrt&]|u[0-9a-fA-F]{4}))*\'"), //'
11
+	T_AOPEN("\\{"),
12
+	T_ACLOSE("\\}"),
13
+	T_SQOPEN("\\["),
14
+	T_SQCLOSE("\\]"),
15
+	T_DOT3("\\.\\.\\."),
16
+	T_DOT2("\\.\\."),
17
+	T_DOT("\\."),
18
+	T_COMMA(","),
19
+	T_INCREMENT("\\+\\+"),
20
+	T_ADDASSIGN("\\+="),
21
+	T_ADD("\\+"),
22
+	T_DECREMENT("\\-\\-"),
23
+	T_SUBASSIGN("\\-="),
24
+	T_SUB("\\-"),
25
+	T_CATASSIGN("~="),
26
+	T_CAT("~"),
27
+	T_MULASSIGN("\\*="),
28
+	T_MUL("\\*"),
29
+	T_DIVASSIGN("/="),
30
+	T_DIV("/"),
31
+	T_MODASSIGN("%="),
32
+	T_MOD("%"),
33
+	T_ORASSIGN("\\|="),
34
+	T_OROR("\\|\\|"),
35
+	T_OR("\\|"),
36
+	T_ANDASSIGN("&="),
37
+	T_ANDAND("&&"),
38
+	T_AND("&"),
39
+	T_XORASSIGN("\\^="),
40
+	T_XOR("\\^"),
41
+	T_COALESCE("\\?\\?"),
42
+	T_QUEST("\\?"),
43
+	T_COLON(":"),
44
+	T_BROPEN("\\("),
45
+	T_BRCLOSE("\\)"),
46
+	T_SEMICOLON(";"),
47
+	T_LESSEQ("<="),
48
+	T_SHLASSIGN("<<="),
49
+	T_SHL("<<"),
50
+	T_LESS("<"),
51
+	T_GREATEREQ(">="),
52
+	T_USHR(">>>"),
53
+	T_USHRASSIGN(">>>="),
54
+	T_SHRASSIGN(">>="),
55
+	T_SHR(">>"),
56
+	T_GREATER(">"),
57
+	T_LAMBDA("=>"),
58
+	T_EQUAL3("==="),
59
+	T_EQUAL2("=="),
60
+	T_ASSIGN("="),
61
+	T_NOTEQUAL2("!=="),
62
+	T_NOTEQUAL("!="),
63
+	T_NOT("!"),
64
+	T_DOLLAR("$"),
65
+	
66
+	K_IMPORT,
67
+	K_ALIAS,
68
+	K_CLASS,
69
+	K_FUNCTION,
70
+	K_INTERFACE,
71
+	K_ENUM,
72
+	K_STRUCT,
73
+	K_EXPAND,
74
+	K_VARIANT,
75
+	
76
+	K_ABSTRACT,
77
+	K_FINAL,
78
+	K_OVERRIDE,
79
+	K_CONST,
80
+	K_PRIVATE,
81
+	K_PUBLIC,
82
+	K_EXPORT,
83
+	K_STATIC,
84
+	K_PROTECTED,
85
+	K_IMPLICIT,
86
+	K_VIRTUAL,
87
+	K_EXTERN,
88
+	
89
+	K_VAL,
90
+	K_VAR,
91
+	K_GET,
92
+	K_IMPLEMENTS,
93
+	K_SET,
94
+	
95
+	K_VOID,
96
+	K_ANY,
97
+	K_BOOL,
98
+	K_BYTE,
99
+	K_SBYTE,
100
+	K_SHORT,
101
+	K_USHORT,
102
+	K_INT,
103
+	K_UINT,
104
+	K_LONG,
105
+	K_ULONG,
106
+	K_FLOAT,
107
+	K_DOUBLE,
108
+	K_CHAR,
109
+	K_STRING,
110
+	
111
+	K_IF,
112
+	K_ELSE,
113
+	K_DO,
114
+	K_WHILE,
115
+	K_FOR,
116
+	K_THROW,
117
+	K_LOCK,
118
+	K_TRY,
119
+	K_CATCH,
120
+	K_FINALLY,
121
+	K_RETURN,
122
+	K_BREAK,
123
+	K_CONTINUE,
124
+	K_SWITCH,
125
+	K_CASE,
126
+	K_DEFAULT,
127
+	
128
+	K_IN,
129
+	K_IS,
130
+	K_AS,
131
+	K_MATCH,
132
+	K_THROWS,
133
+	
134
+	K_SUPER,
135
+	K_THIS,
136
+	K_NULL,
137
+	K_TRUE,
138
+	K_FALSE,
139
+	K_NEW,
140
+	
141
+	INVALID,
142
+	EOF
143
+	;
144
+	
145
+	val regexp as string? : public get;
146
+	val whitespace as bool : public get;
147
+	
148
+	this() {
149
+		this.regexp = null;
150
+		this.whitespace = false;
151
+	}
152
+	
153
+	this(regexp as string) {
154
+		this.regexp = regexp;
155
+		this.whitespace = false;
156
+	}
157
+	
158
+	this(regexp as string, whitespace as bool) {
159
+		this.regexp = regexp;
160
+		this.whitespace = whitespace;
161
+	}
162
+	
163
+	public implements TokenType {}
164
+}

+ 19
- 0
project.json View File

@@ -0,0 +1,19 @@
1
+{
2
+	"targets": [
3
+		{ "module": "shared", "type": "javaSource", "output": "shared/outputJava", "name": "SharedJavaSource" }
4
+	],
5
+	"modules": [
6
+		"shared",
7
+		"codemodel",
8
+		"parser"
9
+	],
10
+	"libraries": {
11
+		"StdLib": {
12
+			"directory": "../StdLibs",
13
+			"modules": [
14
+				"collections",
15
+				"io"
16
+			]
17
+		}
18
+	}
19
+}

+ 4
- 0
shared/module.json View File

@@ -0,0 +1,4 @@
1
+{
2
+	"package": "org.openzen.zencode.shared",
3
+	"host": "universal"
4
+}

+ 38
- 0
shared/src/CodePosition.zs View File

@@ -0,0 +1,38 @@
1
+export struct CodePosition {
2
+	public const BUILTIN as CodePosition  = new CodePosition("builtin", 0, 0, 0, 0);
3
+	public const NATIVE as CodePosition = new CodePosition("native", 0, 0, 0, 0);
4
+	
5
+	val filename as string : get;
6
+	val fromLine as int : get;
7
+	val fromLineOffset as int : get;
8
+	val toLine as int : get;
9
+	val toLineOffset as int : get;
10
+	
11
+	public this(
12
+			filename as string,
13
+			fromLine as int, fromLineOffset as int,
14
+			toLine as int, toLineOffset as int)
15
+	{
16
+		this.filename = filename;
17
+		this.fromLine = fromLine;
18
+		this.fromLineOffset = fromLineOffset;
19
+		this.toLine = toLine;
20
+		this.toLineOffset = toLineOffset;
21
+	}
22
+	
23
+	public toShortString() as string {
24
+		val lastSeparator = filename.lastIndexOf('/');
25
+		val shortFilename = lastSeparator >= 0 ? filename[(lastSeparator + 1) .. $] : filename;
26
+		if fromLine == 0 && fromLineOffset == 0
27
+			return shortFilename;
28
+		
29
+		return shortFilename + ":" + fromLine + ":" + fromLineOffset;
30
+	}
31
+	
32
+	[Precondition(ENFORCE, this.filename == to.filename, "From and to positions must be in the same file!")]
33
+	public ..(to as CodePosition) as CodePosition
34
+		=> new CodePosition(this.filename, this.fromLine, this.fromLineOffset, to.toLine, to.toLineOffset);
35
+	
36
+	public implicit as string
37
+		=> fromLine == 0 && fromLineOffset == 0 ? filename : filename + ":" + fromLine + ":" + fromLineOffset;
38
+}

+ 15
- 0
shared/src/CompileException.zs View File

@@ -0,0 +1,15 @@
1
+public class CompileException : Exception {
2
+	public static internalError(message as string) as CompileException {
3
+		return new CompileException(CodePosition.BUILTIN, INTERNAL_ERROR, message);
4
+	}
5
+	
6
+	val position as CodePosition : get;
7
+	val code as CompileExceptionCode : get;
8
+	
9
+	public this(position as CodePosition, code as CompileExceptionCode, message as string) {
10
+		super((position as string) + ": [" + code + "] " + message);
11
+		
12
+		this.position = position;
13
+		this.code = code;
14
+	}
15
+}

+ 50
- 0
shared/src/CompileExceptionCode.zs View File

@@ -0,0 +1,50 @@
1
+public enum CompileExceptionCode {
2
+	UNEXPECTED_TOKEN,
3
+	IMPORT_NOT_FOUND,
4
+	NO_OUTER_BECAUSE_NOT_INNER,
5
+	NO_OUTER_BECAUSE_STATIC,
6
+	NO_OUTER_BECAUSE_OUTSIDE_TYPE,
7
+	TYPE_ARGUMENTS_INVALID_NUMBER,
8
+	TYPE_ARGUMENTS_NOT_INFERRABLE,
9
+	USING_STATIC_ON_INSTANCE,
10
+	CANNOT_ASSIGN,
11
+	UNAVAILABLE_IN_CLOSURE,
12
+	USING_PACKAGE_AS_EXPRESSION,
13
+	USING_PACKAGE_AS_CALL_TARGET,
14
+	USING_TYPE_AS_EXPRESSION,
15
+	MEMBER_NO_SETTER,
16
+	MEMBER_NO_GETTER,
17
+	MEMBER_NOT_STATIC,
18
+	MEMBER_IS_FINAL,
19
+	MEMBER_DUPLICATE,
20
+	CALL_AMBIGUOUS,
21
+	CALL_NO_VALID_METHOD,
22
+	ENUM_VALUE_DUPLICATE,
23
+	INVALID_CAST,
24
+	NO_SUCH_INNER_TYPE,
25
+	NO_DOLLAR_HERE,
26
+	UNSUPPORTED_XML_EXPRESSIONS,
27
+	UNSUPPORTED_NAMED_ARGUMENTS,
28
+	TYPE_CANNOT_UNITE,
29
+	BRACKET_MULTIPLE_EXPRESSIONS,
30
+	SUPER_CALL_NO_SUPERCLASS,
31
+	LAMBDA_HEADER_INVALID,
32
+	COALESCE_TARGET_NOT_OPTIONAL,
33
+	MULTIPLE_MATCHING_HINTS,
34
+	MISSING_MAP_KEY,
35
+	NO_SUCH_MEMBER,
36
+	USING_THIS_OUTSIDE_TYPE,
37
+	USING_THIS_STATIC,
38
+	UNDEFINED_VARIABLE,
39
+	METHOD_BODY_REQUIRED,
40
+	BREAK_OUTSIDE_LOOP,
41
+	CONTINUE_OUTSIDE_LOOP,
42
+	NO_SUCH_ITERATOR,
43
+	NO_SUCH_TYPE,
44
+	RETURN_VALUE_REQUIRED,
45
+	RETURN_VALUE_VOID,
46
+	INVALID_CONDITION,
47
+	INTERNAL_ERROR,
48
+	CANNOT_SET_FINAL_VARIABLE,
49
+	MISSING_PARAMETER
50
+}

+ 7
- 0
shared/src/SourceFile.zs View File

@@ -0,0 +1,7 @@
1
+public class SourceFile {
2
+	val filename as string : get;
3
+	
4
+	public this(filename as string) {
5
+		this.filename = filename;
6
+	}
7
+}

+ 496
- 0
shared/src/StringExpansion.zs View File

@@ -0,0 +1,496 @@
1
+export expand string {
2
+	static val NAMED_CHARACTER_ENTITIES as CharacterEntity[string];
3
+	
4
+	static {
5
+		val entities = getCharacterEntities();
6
+		
7
+		// TODO: getCharacterEntities().index(e => e.stringValue);
8
+		NAMED_CHARACTER_ENTITIES = {};
9
+		for entity in entities
10
+			NAMED_CHARACTER_ENTITIES[entity.stringValue] = entity;
11
+	}
12
+	
13
+	private static getCharacterEntities() as CharacterEntity[] {
14
+		return [
15
+			{ 'quot', 34 },
16
+			{ 'amp', 38 },
17
+			{ 'apos', 39 },
18
+			{ 'lt', 60 },
19
+			{ 'gt', 62 },
20
+			{ 'nbsp', 160 },
21
+			{ 'iexcl', 161 },
22
+			{ 'cent', 162 },
23
+			{ 'pound', 163 },
24
+			{ 'curren', 164 },
25
+			{ 'yen', 165 },
26
+			{ 'brvbar', 166 },
27
+			{ 'sect', 167 },
28
+			{ 'uml', 168 },
29
+			{ 'copy', 169 },
30
+			{ 'ordf', 170 },
31
+			{'laquo', 171},
32
+			{ 'not', 172 },
33
+			{ 'shy', 173 },
34
+			{ 'reg', 174 },
35
+			{ 'macr', 175 },
36
+			{ 'deg', 176 },
37
+			{ 'plusmn', 177 },
38
+			{ 'sup2', 178 },
39
+			{ 'sup3', 179 },
40
+			{ 'acute', 180 },
41
+			{ 'micro', 181 },
42
+			{ 'para', 182 },
43
+			{ 'middot', 183 },
44
+			{ 'cedil', 184 },
45
+			{ 'sup1', 185 },
46
+			{ 'ordm', 186 },
47
+			{ 'raquo', 187 },
48
+			{ 'frac14', 188 },
49
+			{ 'frac12', 189 },
50
+			{ 'frac34', 190 },
51
+			{ 'iquest', 191 },
52
+
53
+			{ 'Agrave', 192 },
54
+			{ 'Aacute', 193 },
55
+			{ 'Acirc', 194 },
56
+			{ 'Atilde', 195 },
57
+			{ 'Auml', 196 },
58
+			{ 'Aring', 197 },
59
+			{ 'AElig', 198 },
60
+			{ 'Ccedil', 199 },
61
+			{ 'Egrave', 200 },
62
+			{ 'Eacute', 201 },
63
+			{ 'Ecirc', 202 },
64
+			{ 'Euml', 203 },
65
+			{ 'lgrave', 204 },
66
+			{ 'lacute', 205 },
67
+			{ 'lcirc', 206 },
68
+			{ 'luml', 207 },
69
+			{ 'ETH', 208 },
70
+			{ 'Ntilde', 209 },
71
+			{ 'Ograve', 210 },
72
+			{ 'Oacute', 211 },
73
+			{ 'Ocirc', 212 },
74
+			{ 'Otilde', 213 },
75
+			{ 'Ouml', 214 },
76
+			{ 'times', 215 },
77
+			{ 'Oslash', 216 },
78
+			{ 'Ugrave', 217 },
79
+			{ 'Uacute', 218 },
80
+			{ 'Ucirc', 219 },
81
+			{ 'Uuml', 220 },
82
+			{ 'Yacute', 221 },
83
+			{ 'THORN', 222 },
84
+			{ 'szlig', 223 },
85
+			{ 'agrave', 224 },
86
+			{ 'aacute', 225 },
87
+			{ 'acirc', 226 },
88
+			{ 'atilde', 227 },
89
+			{ 'auml', 228 },
90
+			{ 'aring', 229 },
91
+			{ 'aelig', 230 },
92
+			{ 'ccedil', 231 },
93
+			{ 'egrave', 232 },
94
+			{ 'eacute', 233 },
95
+			{ 'ecirc', 234 },
96
+			{ 'euml', 235 },
97
+			{ 'igrave', 236 },
98
+			{ 'iacute', 237 },
99
+			{ 'icirc', 238 },
100
+			{ 'iuml', 239 },
101
+			{ 'eth', 240 },
102
+			{ 'ntilde', 241 },
103
+			{ 'ograve', 242 },
104
+			{ 'oacute', 243 },
105
+			{ 'ocirc', 244 },
106
+			{ 'otilde', 245 },
107
+			{ 'ouml', 246 },
108
+			{ 'divide', 247 },
109
+			{ 'oslash', 248 },
110
+			{ 'ugrave', 249 },
111
+			{ 'uacute', 250 },
112
+			{ 'ucirc', 251 },
113
+			{ 'uuml', 252 },
114
+			{ 'yacute', 253 },
115
+			{ 'thorn', 254 },
116
+			{ 'yuml', 255 },
117
+
118
+			{ 'OElig', 338 },
119
+			{ 'oelig', 339 },
120
+			{ 'Scaron', 352 },
121
+			{ 'scaron', 353 },
122
+			{ 'Yuml', 376 },
123
+
124
+			{ 'fnof', 402 },
125
+
126
+			{ 'circ', 710 },
127
+			{ 'tilde', 732 },
128
+
129
+			{ 'Alpha', 913 },
130
+			{ 'Beta', 914 },
131
+			{ 'Gamma', 915 },
132
+			{ 'Delta', 916 },
133
+			{ 'Epsilon', 917 },
134
+			{ 'Zeta', 918 },
135
+			{ 'Eta', 919 },
136
+			{ 'Theta', 920 },
137
+			{ 'Iota', 921 },
138
+			{ 'Kappa', 922 },
139
+			{ 'Lambda', 923 },
140
+			{ 'Mu', 924 },
141
+			{ 'Nu', 925 },
142
+			{ 'Xi', 926 },
143
+			{ 'Omicron', 927 },
144
+			{ 'Pi', 928 },
145
+			{ 'Rho', 929 },
146
+			{ 'Sigma', 931 },
147
+			{ 'Tau', 932 },
148
+			{ 'Upsilon', 933 },
149
+			{ 'Phi', 934 },
150
+			{ 'Chi', 935 },
151
+			{ 'Psi', 936 },
152
+			{ 'Omega', 937 },
153
+
154
+			{ 'alpha', 945 },
155
+			{ 'beta', 946 },
156
+			{ 'gamma', 947 },
157
+			{ 'delta', 948 },
158
+			{ 'epsilon', 949 },
159
+			{ 'zeta', 950 },
160
+			{ 'eta', 951 },
161
+			{ 'theta', 952 },
162
+			{ 'iota', 953 },
163
+			{ 'kappa', 954 },
164
+			{ 'lambda', 955 },
165
+			{ 'mu', 956 },
166
+			{ 'nu', 957 },
167
+			{ 'xi', 958 },
168
+			{ 'omicron', 959 },
169
+			{ 'pi', 960 },
170
+			{ 'rho', 961 },
171
+			{ 'sigmaf', 962 },
172
+			{ 'sigma', 963 },
173
+			{ 'tau', 964 },
174
+			{ 'upsilon', 965 },
175
+			{ 'phi', 966 },
176
+			{ 'chi', 967 },
177
+			{ 'psi', 968 },
178
+			{ 'omega', 969 },
179
+			{ 'thetasym', 977 },
180
+			{ 'upsih', 978 },
181
+			{ 'piv', 982 },
182
+
183
+			{ 'ensp', 8194 },
184
+			{ 'emsp', 8195 },
185
+			{ 'thinsp', 8201 },
186
+			{ 'zwnj', 8204 },
187
+			{ 'zwj', 8205 },
188
+			{ 'lrm', 8206 },
189
+			{ 'rlm', 8207 },
190
+			{ 'ndash', 8211 },
191
+			{ 'mdash', 8212 },
192
+			{ 'lsquo', 8216 },
193
+			{ 'rsquo', 8217 },
194
+			{ 'sbquo', 8218 },
195
+			{ 'ldquo', 8220 },
196
+			{ 'rdquo', 8221 },
197
+			{ 'bdquo', 8222 },
198
+			{ 'dagger', 8224 },
199
+			{ 'Dagger', 8225 },
200
+			{ 'bull', 8226 },
201
+			{ 'hellip', 8230 },
202
+			{ 'permil', 8240 },
203
+			{ 'prime', 8242 },
204
+			{ 'Prime', 8243 },
205
+			{ 'lsaquo', 8249 },
206
+			{ 'rsaquo', 8250 },
207
+			{ 'oline', 8254 },
208
+			{ 'frasl', 8260 },
209
+
210
+			{ 'euro', 8364 },
211
+
212
+			{ 'image', 8465 },
213
+			{ 'weierp', 8472 },
214
+			{ 'real', 8476 },
215
+			{ 'trade', 8482 },
216
+			{ 'alefsym', 8501 },
217
+
218
+			{ 'larr', 8592 },
219
+			{ 'uarr', 8593 },
220
+			{ 'rarr', 8594 },
221
+			{ 'darr', 8595 },
222
+			{ 'harr', 8596 },
223
+			{ 'crarr', 8629 },
224
+			{ 'lArr', 8656 },
225
+			{ 'uArr', 8657 },
226
+			{ 'rArr', 8658 },
227
+			{ 'dArr', 8659 },
228
+			{ 'hArr', 8660 },
229
+
230
+			{ 'forall', 8704 },
231
+			{ 'part', 8706 },
232
+			{ 'exist', 8707 },
233
+			{ 'empty', 8709 },
234
+			{ 'nabla', 8711 },
235
+			{ 'isin', 8712 },
236
+			{ 'notin', 8713 },
237
+			{ 'ni', 8715 },
238
+			{ 'prod', 8719 },
239
+			{ 'sum', 8721 },
240
+			{ 'minus', 8722 },
241
+			{ 'lowast', 8727 },
242
+			{ 'radic', 8730 },
243
+			{ 'prop', 8733 },
244
+			{ 'infin', 8734 },
245
+			{ 'ang', 8736 },
246
+			{ 'and', 8743 },
247
+			{ 'or', 8744 },
248
+			{ 'cap', 8745 },
249
+			{ 'cup', 8746 },
250
+			{ 'int', 8747 },
251
+			{ 'there4', 8756 },
252
+			{ 'sim', 8764 },
253
+			{ 'cong', 8773 },
254
+			{ 'asymp', 8776 },
255
+			{ 'ne', 8800 },
256
+			{ 'equiv', 8801 },
257
+			{ 'le', 8804 },
258
+			{ 'ge', 8805 },
259
+			{ 'sub', 8834 },
260
+			{ 'sup', 8835 },
261
+			{ 'nsub', 8836 },
262
+			{ 'sube', 8838 },
263
+			{ 'supe', 8839 },
264
+			{ 'oplus', 8853 },
265
+			{ 'otimes', 8855 },
266
+			{ 'perp', 8869 },
267
+			{ 'sdot', 8901 },
268
+
269
+			{ 'lceil', 8968 },
270
+			{ 'rceil', 8969 },
271
+			{ 'lfloor', 8970 },
272
+			{ 'rfloor', 8971 },
273
+			{ 'lang', 9001 },
274
+			{ 'rang', 9002 },
275
+			{ 'loz', 9674 },
276
+			{ 'spades', 9824 },
277
+			{ 'clubs', 8927 },
278
+			{ 'hearts', 9829 },
279
+			{ 'diams', 9830 },
280
+		];
281
+	}
282
+
283
+	
284
+	/**
285
+	 * Unescapes a string escaped in one of following ways:
286
+	 * 
287
+	 * <ul>
288
+	 * <li>A string escaped with single quotes (<code>'Hello "my" world'</code>)</li>
289
+	 * <li>A string escaped with double quotes (<code>"Hello 'my' world"</code>)</li>
290
+	 * <li>A near-literal string (<code>@"C:\Program Files\"</code>) in which escape sequences
291
+	 * aren't processed but the " character cannot occur</li>
292
+	 * </ul>
293
+	 * 
294
+	 * The following escape sequences are recognized:
295
+	 * <ul>
296
+	 * <li>\\</li>
297
+	 * <li>\'</li>
298
+	 * <li>\"</li>
299
+	 * <li>\&amp;namedCharacterEntity; (note that although redundant, \&amp;#ddd; and \&amp;#xXXXX; are also allowed)</li>
300
+	 * <li>\t</li>
301
+	 * <li>\n</li>
302
+	 * <li>\r</li>
303
+	 * <li>\b</li>
304
+	 * <li>\f</li>
305
+	 * <li>\&amp;uXXXX for unicode character points</li>
306
+	 * </ul>
307
+	 * 
308
+	 * @param escapedString escaped string
309
+	 * @return unescaped string
310
+	 */
311
+	[Precondition(ENFORCE, this.length >= 2, "String is not quoted")]
312
+	[Precondition(ENFORCE, (this[0] == '@' && this[1] in ['"', '\'']) || (this[0] in ['"', '\'']), "String is not quoted")]
313
+	[Precondition(ENFORCE, this[0] == this[$ - 1], "Unbalanced quotes")]
314
+	public const unescape() as Result<string, string>
315
+	{
316
+		val isLiteral = this[0] == '@';
317
+		var quoted = this;
318
+		if isLiteral
319
+			quoted = quoted[1 .. $];
320
+		
321
+		val quoteCharacter = this[0];
322
+		if isLiteral
323
+			return Ok(quoted[1 .. $ - 1]);
324
+		
325
+		val result = new StringBuilder(this.length - 2);
326
+		var i = 1;
327
+		while i < quoted.length - 1 {
328
+			if quoted[i] == '\\' {
329
+				if i >= quoted.length - 1
330
+					return Error('Unfinished escape sequence');
331
+				
332
+				switch quoted[i + 1] {
333
+					case '\\':
334
+						i++;
335
+						result << '\\';
336
+						break;
337
+					case '&':
338
+						val characterEntity = try!readCharacterEntity(quoted, i + 1);
339
+						i += characterEntity.stringValue.length + 2;
340
+						result << characterEntity.charValue;
341
+						break;
342
+					case 't': i++; result << '\t'; break;
343
+					case 'r': i++; result << '\r'; break;
344
+					case 'n': i++; result << '\n'; break;
345
+					case 'b': i++; result << '\b'; break;
346
+					case 'f': i++; result << '\f'; break;
347
+					case '"': i++; result << '\"'; break;
348
+					case '\'': i++; result << '\''; break;
349
+					case 'u':
350
+						if i >= quoted.length - 5
351
+							return Error('Unfinished escape sequence');
352
+						val hex0 = try!readHexCharacter(quoted[i + 2]);
353
+						val hex1 = try!readHexCharacter(quoted[i + 3]);
354
+						val hex2 = try!readHexCharacter(quoted[i + 4]);
355
+						val hex3 = try!readHexCharacter(quoted[i + 5]);
356
+						i += 5;
357
+						result << ((hex0 << 12) | (hex1 << 8) | (hex2 << 4) | hex3) as char;
358
+						break;
359
+					default:
360
+						return Error('Illegal escape sequence');
361
+				}
362
+			} else {
363
+				result << quoted[i];
364
+			}
365
+		}
366
+		
367
+		return Ok(result as string);
368
+	}
369
+	
370
+	/**
371
+	 * Escapes special characters in the given string, including ". (but not ').
372
+	 * Adds opening and closing quotes.
373
+	 * 
374
+	 * @param value value to be escaped
375
+	 * @param quote character (' or ")
376
+	 * @param escapeUnicode true to escape any non-ascii value, false to leave them be
377
+	 * @return escaped value
378
+	 */
379
+	public const escape(quote as char, escapeUnicode as bool) as string
380
+	{
381
+		val output = new StringBuilder();
382
+		output << quote;
383
+		for c in this {
384
+			switch c {
385
+				case '"':
386
+					if quote == '"'
387
+						output << "\\\"";
388
+					break;
389
+				case '\'':
390
+					if quote == '\''
391
+						output << "\\\'";
392
+					break;
393
+				case '\n': output << "\\n"; break;
394
+				case '\r': output << "\\r"; break;
395
+				case '\t': output << "\\t"; break;
396
+				default:
397
+					if escapeUnicode && c > 127 {
398
+						output << "\\u" << (c as int).toHexString().lpad(4, '0');
399
+					} else {
400
+						output << c;
401
+					}
402
+			}
403
+		}
404
+		
405
+		output << quote;
406
+		return output;
407
+	}
408
+	
409
+	/**
410
+	 * Reads a single hex digit and converts it to a value 0-15.
411
+	 * 
412
+	 * @param hex hex digit
413
+	 * @return converted value
414
+	 */
415
+	private static readHexCharacter(hex as char) as Result<int, string>
416
+	{
417
+		if hex >= '0' && hex <= '9'
418
+			return Ok(hex - '0');
419
+		
420
+		if hex >= 'A' && hex <= 'F'
421
+			return Ok(hex - 'A' + 10);
422
+		
423
+		if hex >= 'a' && hex <= 'f'
424
+			return Ok(hex - 'a' + 10);
425
+		
426
+		return Error("Illegal hex character: " + hex);
427
+	}
428
+	
429
+	/**
430
+	 * Reads a single character entity (formatted as &amp;characterEntity;) at the
431
+	 * given string offset.
432
+	 * 
433
+	 * The following formats are supported:
434
+	 * <ul>
435
+	 * <li>&amp;namedCharacterEntity;</li>
436
+	 * <li>&amp;#ddd</li>
437
+	 * <li>&amp;#xXXXX</li>
438
+	 * </ul>
439
+	 * 
440
+	 * The returned value includes the character entity, without the enclosing
441
+	 * &amp; and ; characters.
442
+	 * 
443
+	 * @param str string value to search in
444
+	 * @param offset offset to look at
445
+	 * @return character entity
446
+	 * @throws IllegalArgumentException if the given string does not contain a
447
+	 *	valid character entity at the given position
448
+	 */
449
+	[Precondition(ENFORCE, str[offset] == '&', 'Not a proper character entity')]
450
+	private static readCharacterEntity(str as string, offset as int) as Result<CharacterEntity, string>
451
+	{
452
+		if offset + 3 >= str.length
453
+			return Error('Incomplete character entity');
454
+		
455
+		val semi = str.indexOf(';', offset);
456
+		if semi < 0
457
+			return Error('Incomplete character entity');
458
+		
459
+		val entity = str[(offset + 1) .. semi];
460
+		if entity.isEmpty
461
+			return Error('Character entity cannot be empty');
462
+		
463
+		if entity in NAMED_CHARACTER_ENTITIES
464
+			return Ok(NAMED_CHARACTER_ENTITIES[entity]);
465
+		
466
+		if entity[0] == '#' {
467
+			if entity.length < 2
468
+				return Error('Character entity number too short');
469
+			
470
+			if str[1] == 'x' {
471
+				// hex character entity
472
+				if entity.length != 7
473
+					return Error('Hexadecimal character entity must have 4 hex digits');
474
+				
475
+				val ivalue = int.parse(entity[2 .. $], 16);
476
+				return Ok(new CharacterEntity(entity, ivalue as char));
477
+			} else {
478
+				// decimal character entity
479
+				val ivalue = int.parse(entity[1 .. $]);
480
+				return Ok(new CharacterEntity(entity, ivalue as char));
481
+			}
482
+		}
483
+		
484
+		return Error('Not a valid named character entity');
485
+	}
486
+}
487
+
488
+public struct CharacterEntity {
489
+	val charValue as char : get;
490
+	val stringValue as string : get;
491
+	
492
+	public this(stringValue as string, charValue as char) {
493
+		this.charValue = charValue;
494
+		this.stringValue = stringValue;
495
+	}
496
+}

+ 12
- 0
shared/src/Taggable.zs View File

@@ -0,0 +1,12 @@
1
+public virtual class Taggable {
2
+	val tags as T[<T>] = {};
3
+	
4
+	public setTag<T>(tag as T) as void
5
+		=> tags.put<T>(tag);
6
+	
7
+	public getTag<T>() as T?
8
+		=> tags.getOptional<T>();
9
+	
10
+	public hasTag<T>() as bool
11
+		=> tags.contains<T>();
12
+}

Loading…
Cancel
Save