Skip to content

Commit

Permalink
Finish generics support
Browse files Browse the repository at this point in the history
  • Loading branch information
1Axen committed Mar 16, 2024
1 parent 8e0caa0 commit bcc9bd2
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 47 deletions.
42 changes: 41 additions & 1 deletion docs/Using.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ enum State = { Starting, Started, Stopping, Stopped }
```
## Structs
You can define structs using the `struct` keyword
Structs can also hold structs within.
Structs can also hold structs within:
```
struct Entity {
Identifier: u8,
Expand All @@ -110,6 +110,46 @@ struct Entity {
}?
}
```

### Generics
---
Structs support the use of generic type parameters, a generic is simply a type which allows you to slot in any other type, generics can be very handy in reducing repetition.
```
struct Packet<T> {
Sequence: u16,
Ack: u16,
Data: T
}
struct Entity {
Identifier: u8,
Health: u8(0..100),
Angle: u16,
Position: vector
}
struct Command {
X: u8,
Y: u8,
Z: u8,
-- ...
}
event Snapshot {
From: Server,
Type: Unreliable,
Call: SingleSync,
Data: Packet<Entity[]>
}
event Command {
From: Server,
Type: Unreliable,
Call: SingleSync,
Data: Packet<Command>
}
```
In the code above we have a simple packet transmission protocol which contains the current packets identifier (Sequence), the last recieved packet (Ack) and a generic data field. Instead of repeating the contents of `Packet` everytime we need to send something over the wire we can take advantage of generics to automatically fill the `Data` field with whatever we need to transmit.
## Maps
You can define maps using the `map` keyword
> [!NOTE]
Expand Down
22 changes: 20 additions & 2 deletions src/Generator/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,24 @@ function Generators.LuauType(Declaration: Parser.TypeDeclaration, UseTypeAsValue
end
end

if Value.Parameters then
local Parameters = ""
local Maximum = #Value.Parameters

--> Generate luau types for parameters
for Index, Parameter in Value.Parameters do
Parameters ..= Generators.LuauType(Parameter)

if Index ~= Maximum then
Parameters ..= ","
end
end

--> Wrap in chevrons
Parameters = `<{Parameters}>`
Type = `{Type}{Parameters}`
end

--> Generalized type generation, works for everything except tuples
if Declaration.Type ~= "Tuple" then
Values = `Value: {UseTypeAsValue and Type or Export}`
Expand All @@ -756,14 +774,14 @@ end
function Generators.Type(Declaration: Type)
local Value = Declaration.Value
local Identifier = Value.Identifier

local Type, Values, Export, Returns = Generators.LuauType(Declaration)

local Read = Blocks.Function(GetTypesPath(Identifier, false), "", `({Export})`)
local Write = Blocks.Function(GetTypesPath(Identifier, true), Values, "()")

local Generics = ""
if Value.Generics then
for Generic in Value.Generics do
for Generic in Value.Generics.Indices do
Generics ..= `{Generic},`
end

Expand Down
6 changes: 4 additions & 2 deletions src/Lexer.luau
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ local Settings = require("./Settings")
export type Types =
"Comma" | "OpenParentheses" | "CloseParentheses" | "OpenBraces" | "CloseBraces" | "OpenBrackets" | "CloseBrackets" --> Structs & enums
| "String" | "Boolean" | "Number" --> Literals
| "Array" | "Range" | "Optional" | "Class" | "Component" --> Attributes
| "Array" | "Range" | "Optional" | "Class" | "Component" | "OpenChevrons" | "CloseChevrons" --> Attributes
| "Assign" | "FieldAssign" | "Keyword" | "Primitive" | "Identifier" --> Reserved
| "Whitespace" | "Comment" | "Unknown" | "EndOfFile"

Expand Down Expand Up @@ -40,6 +40,8 @@ local Matches = {
{"^:", "FieldAssign"},
{"^{", "OpenBraces"},
{"^}", "CloseBraces"},
{"^<", "OpenChevrons"},
{"^>", "CloseChevrons"},
{"^,", "Comma"},

--> Comments
Expand All @@ -56,7 +58,7 @@ local Matches = {
{`^%({Number}..{Number}%)`, "Range"},
{`^%[{Number}]`, "Array"},
{`^%[{Number}..{Number}]`, "Array"},
{`^%b<>`, "Component"},
--{`^%b<>`, "Component"},

{"^%(", "OpenParentheses"},
{"^%)", "CloseParentheses"},
Expand Down
3 changes: 2 additions & 1 deletion src/Modules/Error.luau
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ local Error = {
AnalyzeInvalidRangeType = 3008,
AnalyzeInvalidRange = 3009,
AnalyzeDuplicateDeclaration = 3010,
AnalyzeDuplicateTypeGeneric = 3011
AnalyzeDuplicateTypeGeneric = 3011,
AnalyzeInvalidGenerics = 3012,
}

Error.__index = Error
Expand Down
145 changes: 107 additions & 38 deletions src/Parser.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
--!optimize 2

local Lexer = require("./Lexer")
local Table = require("./Modules/Table")
local Error = require("./Modules/Error")
local Settings = require("./Settings")

Expand Down Expand Up @@ -30,8 +31,9 @@ type Node<Type, Body, Tokens> = {
}

type Generics = {
Keys: {[string]: number},
Ordered: {{{Key: string, Values: {[any]: any}}}}
Total: number,
Keys: {[number]: string},
Indices: {[string]: number}
}

export type Scope = {
Expand Down Expand Up @@ -60,6 +62,7 @@ type Attributes = {
Range: NumberRange?,
Optional: boolean?,
Generics: Generics?,
Parameters: {TypeDeclaration}?,
Components: {string}?
}

Expand Down Expand Up @@ -320,8 +323,8 @@ end

---- Utility Parsing Functions ----

function Parser.GetReference(self: Parser, Bucket: Bucket, Identifier: Token): Reference?
local Path = string.split(Identifier.Value, ".")
function Parser.GetReference(self: Parser, Bucket: Bucket, Identifier: string): Reference?
local Path = string.split(Identifier, ".")
local Scope: Scope? = self.Scope

local Offset = 1
Expand Down Expand Up @@ -378,37 +381,109 @@ function Parser.GetGenerics(self: Parser): Generics?
if self.Generics then
return self.Generics
end

local Token = self:TryConsume("Component")
if not Token then

if not self:TryConsume("OpenChevrons") then
return
end

--> Remove whitespace and seperate into identifiers
local Value = string.gsub(Token.Value, "[%s<>]+", "")
local Identifiers = string.split(Value, ",")

--> Parse generics
local Generics: Generics = {
Total = 0,
Keys = {},
Ordered = {}
Indices = {}
}

for Index, Identifier in Identifiers do
if Generics[Identifier] then
Error.new(Error.AnalyzeDuplicateTypeGeneric, self.Source, `Duplicate type parameter "{Identifier}"`)
:Primary(Token, `Type parameter "{Identifier}" was already used`)
:Emit()
while true do
if self:TryConsume("CloseChevrons") then
break
end

Generics.Ordered[Index] = {}
Generics.Keys[Identifier] = Index
local Index = (Generics.Total + 1)
local Token = self:Consume("Identifier")
local Identifier = Token.Value

Generics.Total = Index
Generics.Keys[Index] = Identifier
Generics.Indices[Identifier] = Index

if self:Peek().Type ~= "CloseChevrons" then
self:Consume("Comma")
end
end

self.Generics = Generics
return Generics
end

function Parser.SolveGenerics(self: Parser, Identifier: Token, Declaration: StructDeclaration)
local Generics = Declaration.Value.Generics
if not Generics then
return
end

local OpenToken = self:TryConsume("OpenChevrons")
if not OpenToken then
Error.new(Error.AnalyzeInvalidGenerics, self.Source, `Type expects parameters list`)
:Primary(self:Peek(), "")
:Emit()

return
end

local Maximum = Generics.Total
local Parameters = {}
local References: {[string]: TypeDeclaration} = {}

for Index, Generic in Generics.Keys do
local Type = self:Type(Identifier)
References[Generic] = Type
table.insert(Parameters, Type)

if Index ~= Maximum then
self:Consume("Comma")
end
end

local CloseToken = self:Consume("CloseChevrons")
if #Parameters ~= Maximum then
Error.new(Error.AnalyzeInvalidGenerics, self.Source, `Type expects {Maximum} parameters, but {#Parameters} are specified`)
:Primary({Start = OpenToken.Start, End = CloseToken.End}, "")
:Emit()
end

--> Solve references
Declaration = Table.DeepClone(Declaration)

local function SolveValues(Values: {TypeDeclaration})
for Index, Field in Values do
if Field.Type == "Struct" then
SolveValues(Field.Value.Values)
continue
end

if Field.Type ~= "Generic" then
continue
end

local Value = Field.Value
local Generic = Value.Generic

--> Create a unique reference for each generic
local Reference = References[Generic]
Reference = Table.DeepClone(Reference)
;(Reference :: Declaration).Value.Identifier = Value.Identifier

--> Set value to reference
Values[Index] = Reference
end
end

Declaration.Value.Generics = nil
SolveValues(Declaration.Value.Values)

return Declaration, Parameters
end

function Parser.GetTypeAttributes(self: Parser, Primitive: Settings.Primitive?): (Attributes, AttributesTokens)
local function TryToParseOptional(): (boolean, Token?)
local Token = self:TryConsume("Optional")
Expand Down Expand Up @@ -585,6 +660,7 @@ function Parser.GetTypeAttributes(self: Parser, Primitive: Settings.Primitive?):
}
end


---- Top-Level Parsing Functions ----

function Parser.Parse(self: Parser, Source: string): Body
Expand Down Expand Up @@ -655,7 +731,7 @@ function Parser.Declarations(self: Parser): {Declaration}
--> Prevent duplicates
local Type = KEYWORDS[Keyword.Value]
local Bucket: Bucket = BUCKETS[Type]
local Reference = self:GetReference(Bucket, Identifier)
local Reference = self:GetReference(Bucket, Identifier.Value)

if Reference then
Error.new(Error.AnalyzeDuplicateDeclaration, self.Source, `Duplicate declaration`)
Expand Down Expand Up @@ -887,6 +963,13 @@ function Parser.Type(self: Parser, Identifier: Token, Keyword: Token?, IsDataFie
Declaration = self:Primitive(Identifier)
elseif Type == "Identifier" then
Declaration = self:Reference(Identifier)
if Declaration.Type == "Reference" then
local Value = Declaration.Value
local Referencing = Value.Declaration
if Referencing.Value.Generics then
Value.Declaration, Value.Parameters = self:SolveGenerics(Identifier, Referencing :: StructDeclaration)
end
end
elseif Type == "OpenParentheses" and IsDataField then
Declaration = self:Tuple(Identifier)
end
Expand All @@ -905,7 +988,6 @@ function Parser.Type(self: Parser, Identifier: Token, Keyword: Token?, IsDataFie
Value.Range, Value.Optional, Value.Array, Value.Components = Attributes.Range, Attributes.Optional, Attributes.Array, Attributes.Components
Tokens.Range, Tokens.Optional, Tokens.Array = AttributesTokens.Range, AttributesTokens.Optional, AttributesTokens.Array


return Declaration
end

Expand Down Expand Up @@ -1082,7 +1164,8 @@ function Parser.Reference(self: Parser, Identifier: Token): (ReferenceDeclaratio

--> Generics reference resolution
local Generics = self.Generics
local Generic = Generics and Generics[Token.Value]
local Generic = Generics and Generics.Indices[Token.Value]

if Generic then
return {
Type = "Generic",
Expand All @@ -1095,32 +1178,18 @@ function Parser.Reference(self: Parser, Identifier: Token): (ReferenceDeclaratio
end

--> Normal reference resolution
local Reference = self:GetReference("Types", Token)
local Reference = self:GetReference("Types", Token.Value)
if not Reference then
error(Error.new(Error.AnalyzeUnknownReference, self.Source, `Unknown reference`)
:Primary(Token, "Unknown reference")
:Emit())
end

local Declaration = (Reference :: Reference).Declaration
local TypeGenerics: Generics? = Declaration.Value.Generics
if TypeGenerics then
local ProvidedTypes = self:Consume("Component")

local Value = ProvidedTypes.Value
Value = string.gsub(Value, "%s<>", "")

local Identifiers = string.split(Value, ",")
for Index, Identifier in Identifiers do
local Reference = self:GetReference("Types", {})
end
end

return {
Type = "Reference",
Value = {
Identifier = Identifier.Value,
Declaration = (Reference :: Reference).Declaration :: TypeDeclaration
Declaration = Reference.Declaration
},
Tokens = {}
} :: ReferenceDeclaration
Expand Down
Loading

0 comments on commit bcc9bd2

Please sign in to comment.