From eafe54dc6adae5960af6c2b93f90deac7d97abb0 Mon Sep 17 00:00:00 2001 From: simnJS Date: Tue, 12 May 2026 12:53:47 +0000 Subject: [PATCH 1/2] Add support for Verse programming language --- .gitmodules | 3 + grammars.yml | 2 + lib/linguist/languages.yml | 10 +- samples/Verse/ConcurrencyFeatures.verse | 64 +++ samples/Verse/VerseFeatures.verse | 410 ++++++++++++++++++ vendor/README.md | 1 + vendor/grammars/verse-grammar | 1 + .../git_submodule/verse-grammar.dep.yml | 33 ++ 8 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 samples/Verse/ConcurrencyFeatures.verse create mode 100644 samples/Verse/VerseFeatures.verse create mode 160000 vendor/grammars/verse-grammar create mode 100644 vendor/licenses/git_submodule/verse-grammar.dep.yml diff --git a/.gitmodules b/.gitmodules index b1350e9350..e07cfe11c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1346,6 +1346,9 @@ [submodule "vendor/grammars/verilog.tmbundle"] path = vendor/grammars/verilog.tmbundle url = https://github.com/textmate/verilog.tmbundle +[submodule "vendor/grammars/verse-grammar"] + path = vendor/grammars/verse-grammar + url = https://github.com/simnJS/verse-grammar.git [submodule "vendor/grammars/vsc-ember-syntax"] path = vendor/grammars/vsc-ember-syntax url = https://github.com/lifeart/vsc-ember-syntax.git diff --git a/grammars.yml b/grammars.yml index 5a4b7d8136..8b5e473c64 100644 --- a/grammars.yml +++ b/grammars.yml @@ -1199,6 +1199,8 @@ vendor/grammars/typst-grammar: - source.typst vendor/grammars/verilog.tmbundle: - source.verilog +vendor/grammars/verse-grammar: +- source.verse vendor/grammars/vsc-ember-syntax: - inline.hbs - inline.template diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index c11c9046d6..4ec8bf583e 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -7682,7 +7682,7 @@ TMDL: extensions: - ".tmdl" aliases: - - "Tabular Model Definition Language" + - Tabular Model Definition Language tm_scope: source.tmdl ace_mode: text language_id: 769162295 @@ -8269,6 +8269,14 @@ Verilog: codemirror_mode: verilog codemirror_mime_type: text/x-verilog language_id: 387 +Verse: + type: programming + color: "#518ef8" + extensions: + - ".verse" + tm_scope: source.verse + ace_mode: text + language_id: 180832205 Vim Help File: type: prose color: "#199f4b" diff --git a/samples/Verse/ConcurrencyFeatures.verse b/samples/Verse/ConcurrencyFeatures.verse new file mode 100644 index 0000000000..c9743ab59b --- /dev/null +++ b/samples/Verse/ConcurrencyFeatures.verse @@ -0,0 +1,64 @@ +using. /Verse.org/Concurrency +using. /Verse.org/Simulation + +ConcurrencyFeatures := module { + + ArrayRace(Array:[]t, HandlerFunction(Element:t):u where t:type, u:type):u = { + if ( + Array.Length > 1 + MidPoint := Floor(Array.Length / 2) + LeftArray := Array.Slice[0, MidPoint] + RightArray := Array.Slice[MidPoint] + ) { + race { + ArrayRace(LeftArray, HandlerFunction) + ArrayRace(RightArray, HandlerFunction) + } + } else if (FirstElement := Array[0]) { + HandlerFunction(FirstElement) + } else { + Sleep(Inf) + Err("Unreachable") + } + } + + ArrayRush(Array:[]t, HandlerFunction(Element:t):u where t:type, u:type):u = { + if ( + Array.Length > 1 + MidPoint := Floor(Array.Length / 2) + LeftArray := Array.Slice[0, MidPoint] + RightArray := Array.Slice[MidPoint] + ) { + rush { + ArrayRush(LeftArray, HandlerFunction) + ArrayRush(RightArray, HandlerFunction) + } + } else if (FirstElement := Array[0]) { + HandlerFunction(FirstElement) + } else { + Sleep(Inf) + Err("Unreachable") + } + } + + ArraySync(Array:[]t, HandlerFunction(Element:t):u where t:type, u:type):[]u = { + if ( + Array.Length > 1 + MidPoint := Floor(Array.Length / 2) + LeftArray := Array.Slice[0, MidPoint] + RightArray := Array.Slice[MidPoint] + ) { + SyncResult := sync { + ArraySync(LeftArray, HandlerFunction) + ArraySync(RightArray, HandlerFunction) + } + + SyncResult(0) + SyncResult(1) + + } else if (FirstElement := Array[0]) { + array{HandlerFunction(FirstElement)} + } else { + array{} + } + } +} \ No newline at end of file diff --git a/samples/Verse/VerseFeatures.verse b/samples/Verse/VerseFeatures.verse new file mode 100644 index 0000000000..08a60e4f40 --- /dev/null +++ b/samples/Verse/VerseFeatures.verse @@ -0,0 +1,410 @@ +using. /Fortnite.com/Characters +using. /Fortnite.com/Devices +using. /UnrealEngine.com/Temporary +using. /Verse.org/Random +using. /Verse.org/Simulation + +using. Wrappers + +VerseFeatures := module { + + ### Runtime Utils + + SleepTicks(Count:int):void = for (X := 1..Count). Sleep(0.0) + + ### Logic Utils + + Nothing():void = void + Success():void = void + Failure():false = false? and Err("Unreachable") + + # Unsafe function call, it expect to always succeed, or else will throw a runtime error. + # + # It was made for usage on places where the result is already known to always succeed, such + # as defining constants and avoiding patterns like `Something[] or Err("Unreachable")` + Must(Function:type{_(:t):t2}, Args:t where t:type, t2:type):t2 = { + Function[Args] or Err("This is your fault.") + } + Must(Result:result(t, any) where t:type):t = { + Result.GetSuccess[] or Err("This is your fault.") + } + + (VerseFeatures:)ToString(Input:logic):string = { + if (Input?) { "true" } else { "false" } + } + + CoinFlip():logic = logic{GetRandomInt(0, 1) = 1} + + (Logic:logic).Invert():logic = logic{not Logic?} + + prefix'-'(Logic:logic):logic = Logic.Invert() + + + + ### Agent Utils + + IsRealPlayer(Player:agent):player = player[Player] + + (Agent:agent).IsValid():void = { + player[Agent].IsActive[] or not player[Agent] + Agent.GetFortCharacter[].IsActive[] + } + + (Agent:agent).AwaitValidation(?MaxTimeout:float=Inf, ?RefreshInterval:float=0.1):logic = { + CallbackEvent := event(agent){} + branch { + loop { + if (Agent.IsValid[]). CallbackEvent.Signal(Agent) + Sleep(Max(0.0, RefreshInterval)) + } + } + + CallbackEvent.AwaitFor(Agent, ?MaxTimeout := MaxTimeout) + } + + + + ### Message Utils + + JoinMessages(First:message, Second:message):message = "{First}{Second}" + + operator'+'(First:message, Second:message):message = "{First}{Second}" + + + + ### Map Operations + + # Returns an array with all keys of the map + (Input:[k]v where k:subtype(comparable), v:type).Keys():[]k = { + for (CurrentKey->_Unused : Input). CurrentKey + } + + # Returns an array with all values of the map + (Input:[k]v where k:subtype(comparable), v:type).Values():[]v = { + for (CurrentValue : Input). CurrentValue + } + + # Merges an array of maps into a single map. + # In case of key conflicts, later maps override earlier ones. + ReduceMaps(InputMaps:[][k]v where k:subtype(comparable), v:type):[k]v = { + InputMapsLength := InputMaps.Length + + FirstMap := InputMaps[0] or (return map{}) + SecondMap := InputMaps[1] or (return FirstMap) + + MergedMap := ConcatenateMaps(FirstMap, SecondMap) + + OtherMaps := InputMaps.Slice[2] or Err("Unreachable") + + if (OtherMaps.Length = 0). return MergedMap + + return ReduceMaps(array{MergedMap} + OtherMaps) + } + + # Returns a map without the map element that Key matches the Key provided + # If Strict is set to true, will force a fail when key does not exist on the map + (Input:[k]v).RemoveByKey(KeyToRemove:k, (?Strict:logic=false) where k:subtype(comparable), v:type):[k]v = { + if (Strict?). Input[KeyToRemove] + + var TempMap:[k]v = map{} + for ( + CurrentKey->CurrentValue:Input + CurrentKey <> KeyToRemove + ). set TempMap[CurrentKey] = CurrentValue + TempMap + } + + # Returns a map without all map elements that Key matches the Keys provided + (Input:[k]v).RemoveByKeys(KeysToRemove:[]k where k:subtype(comparable), v:type):[k]v = { + var TempMap:[k]v = Input + for (KeyToRemove : KeysToRemove) { + ResultMap := TempMap.RemoveByKey[KeyToRemove, ?Strict := false] # Strict default value is already false on the function definition and due to that is not a required argument, but if not mentioned again here, for some reason it crashes the server + set TempMap = ResultMap + } + TempMap + } + + # Returns a map with only the elements that Key matches the Keys provided + (Input:[k]v).FilterByKeys(Keys:[]k where k:subtype(comparable), v:type):[k]v = { + var TempMap:[k]v = map{} + for ( + CurrentKey->CurrentValue:Input + KeyToCompare : Keys + CurrentKey = KeyToCompare + ). set TempMap[CurrentKey] = CurrentValue + TempMap + } + + # Returns the first map Key where the associated Value matches the provided Value + (Input:[k]v).FindKeyByValue(Value:v where k:subtype(comparable), v:subtype(comparable)):k = { + var MaybeKey:?k = false + for ( + CurrentKey->CurrentValue:Input + not MaybeKey? + CurrentValue = Value + ). set MaybeKey = option{CurrentKey} + MaybeKey? + } + + # Returns all map Keys where the associated Value matches the provided Value + # If Strict is set to true, will force a fail when none of the values exist on the map + (Input:[k]v).FindKeysByValue(Value:v, (?Strict:logic=false) where k:subtype(comparable), v:subtype(comparable)):[]k = { + KeysFound := for ( + CurrentKey->CurrentValue:Input + CurrentValue = Value + ). CurrentKey + + KeysFound.Length > 0 or not Strict? + + KeysFound + } + + # Return a map with only the elements that Value matches the Values provided + # If Strict is set to true, will force a fail when none of the values exist on the map + (Input:[k]v).FilterByValues(Values:[]v, (?Strict:logic=false) where k:subtype(comparable), v:subtype(comparable)):[k]v = { + var TempMap:[k]v = map{} + for ( + CurrentKey->CurrentValue:Input + ValueToCompare : Values + CurrentValue = ValueToCompare + ). set TempMap[CurrentKey] = CurrentValue + + TempMap.Length > 0 or not Strict? + + TempMap + } + + # Returns the first map Key where the Value succeeds the provided condition function + (Input:[k]v).FindKeyByValueData(Condition:type{_(:v):void} where k:subtype(comparable), v:type):k = { + var MaybeKey:?k = false + for ( + CurrentKey->CurrentValue:Input + not MaybeKey? + Condition[CurrentValue] + ). set MaybeKey = option{CurrentKey} + MaybeKey? + } + + # Returns all map Keys where the Value succeeds the provided condition function + # If Strict is set to true, will force a fail when no compatible values exist on the map + (Input:[k]v).FindKeysByValueData(Condition:type{_(:v):void}<#, (?Strict:logic=false)#> where k:subtype(comparable), v:type):[]k = { + KeysFound := for ( + CurrentKey->CurrentValue:Input + Condition[CurrentValue] + ). CurrentKey + + <#KeysFound.Length > 0 or not Strict?#> + + KeysFound + } + + + + ### Array Operations + + <#> NOTE: Some functions bellow are written to be easy to understand, not necessarily optimized for performance. + Many array operations makes copies of the entire array even when is not needed, and some iterations does + not have early breaks/exists, making it less efficient due to always iterating over all array values. + + (Input:[]t where t:type).Reverse():[]t = { + LastIndex := Input.Length - 1 + for ( + CurrentIndex->_Unused : Input + Element := Input[LastIndex - CurrentIndex] + ). Element + } + + NormalizeIndex(Length:int, Index:int):int = { + Length >= 1 + + NormalizedRange := Mod[Index, Length] + + NormalizedIndex := if (NormalizedRange < 0). Length + NormalizedRange + else. NormalizedRange + } + + (Input:[]t where t:type).At(Index:int):t = { + Input[NormalizeIndex[Input.Length, Index]] + } + + (Input:[]t where t:subtype(comparable)).FindLast(ElementToFind:t):int = { + LastIndex := Input.Length - 1 + + var MaybeIndex:?int = false + for ( + Index := 0..LastIndex + not MaybeIndex? + InverseIndex := LastIndex - Index + Input[InverseIndex] = ElementToFind + ). set MaybeIndex = option{InverseIndex} + + MaybeIndex? + } + + (Input:generator(t) where t:type).AsArray():[]t = { + for (Element : Input). Element + } + + (Input:generator(t) where t:type).GetFirst():t = { + Input.AsArray()[0] + } + + # Return the Index of the first Element on the array that succeeds the provided condition function + (Input:[]t).FindBy(Condition:type{_(:t):void} where t:type):int = { + var MaybeIndex:?int = false + for ( + Index->Element:Input + not MaybeIndex? + Condition[Element] + ). set MaybeIndex = option{Index} + MaybeIndex? + } + + # Return the Index of the first Element on the array that succeeds the provided condition function with additional arguments + (Input:[]t).FindByDynamic(Condition:type{_(:t, :t2):void}, ConditionArgs:t2 where t:type, t2:type):int = { + var MaybeIndex:?int = false + for ( + Index->Element:Input + not MaybeIndex? + Condition[Element, ConditionArgs] + ). set MaybeIndex = option{Index} + MaybeIndex? + } + + # Return an array with only the Elements that succeeds the provided condition function + # If Strict is set to true, will force a fail when no compatible elements exist on the array + (Input:[]t).FilterBy(Condition:type{_(:t):void}, (?Strict:logic=false) where t:type):[]t = { + NewArray := for ( + CurrentValue:Input + Condition[CurrentValue] + ). CurrentValue + + NewArray.Length > 0 or not Strict? + + NewArray + } + + # Return an array with only the Elements that succeeds the provided condition function with additional arguments + # If Strict is set to true, will force a fail when no compatible elements exist on the array + (Input:[]t).FilterByDynamic(Condition:type{_(:t, :t2):void}, ConditionArgs:t2, (?Strict:logic=false) where t:type, t2:type):[]t = { + NewArray := for ( + CurrentValue:Input + Condition[CurrentValue, ConditionArgs] + ). CurrentValue + + NewArray.Length > 0 or not Strict? + + NewArray + } + + + + ### Primitive Math Sorting Operations + + SortIntAscending(Left:int, Right:int):void = { + Left < Right + } + + SortIntDescending(Left:int, Right:int):void = { + not SortIntAscending[Left, Right] + } + + SortFloatAscending(Left:float, Right:float):void = { + Left < Right + } + + SortFloatDescending(Left:float, Right:float):void = { + not SortFloatAscending[Left, Right] + } + + + + ### Containers + + # Generic container class that can be used for FIFO/LIFO/Linked Lists and other common types of array data storage implementations + <#> NOTE: due to current verse limitations, we can't have mutable members (variables) in parametric classes, and for + that reason we need to handle and keep track of the container state changes outside of this class during usage. + Container(t:type) := class { + Elements :[]t = array{} + + PointerPosition:int = 0 + + Size():int = Elements.Length + IsEmpty():void = Elements.Length = 0 + IsPointerValid(?Position:int = PointerPosition):void = Position >= 0 and Position < Elements.Length # Should only fail if has 0 elements due to the class automatically sanitizing the pointer position + + SetPointerPosition(Position:int, ?Circular:logic = false):Container(t) = Container(t){ + PointerPosition := if (Circular?) { + NormalizeIndex[Elements.Length, Position] + } else { + IsPointerValid[?Position := Position] + Position + } + Elements := Elements + } + SetPointerOffset(Offset:int, ?Circular:logic = false):Container(t) = SetPointerPosition[PointerPosition + Offset, ?Circular := Circular] + + AddFirst(Element:t):Container(t) = Container(t){ + PointerPosition := PointerPosition + 1 + Elements := array{Element} + Elements + } + AddLast(Element:t):Container(t) = Container(t){ + PointerPosition := PointerPosition + Elements := Elements + array{Element} + } + + # Get the value at an specific index + # Fails if not able to find a value on the provided index + # If Strict is set to false (Default), out of bound indexes will be normalized to array bounds + # If Strict is set to true, out of bound indexes will not be normalized and will cause a fail + GetAtIndex(Index:int, ?Strict:logic=false):tuple(Container(t), t) = { + TargetIndex := if (Strict?). Index else. NormalizeIndex[Elements.Length, Index] + + (Container(t){ + PointerPosition := if (PointerPosition < TargetIndex). PointerPosition else. Max(0, Min(PointerPosition - 1, Elements.Length - 2)) + Elements := Elements.RemoveElement[TargetIndex] + }, Elements[TargetIndex]) + } + + GetAtPointer():t = Elements[PointerPosition] + + GetFirst():tuple(Container(t), t) = ( + Container(t){ + PointerPosition := Max(PointerPosition - 1, 0) + Elements := Elements.RemoveElement[0] + }, + Elements[0] + ) + + GetLast():tuple(Container(t), t) = ( + Container(t){ + PointerPosition := Max(0, Min(PointerPosition, Elements.Length - 1)) + Elements := Elements.RemoveElement[Elements.Length - 1] + }, + Elements[Elements.Length - 1] + ) + + ### WIP Future Planned Features: + # AddAt + # Find + # FindAll + # Reduce ? + # Splice + + # `(_Self:Container(t))` is a fix for (local:) not being allowed in class scope + (_Self:Container(t)).SortBy(Less:type {_(:t, :t):void}):Container(t) = Container(t){ + PointerPosition := PointerPosition + Elements := SortBy(Elements, Less) + } + + # Aliases + PreviousPointer(?Circular:logic = false):Container(t) = SetPointerOffset[-1, ?Circular := Circular] + NextPointer(?Circular:logic = false):Container(t) = SetPointerOffset[1, ?Circular := Circular] + + Push(Element:t):Container(t) = AddLast(Element) + Pop():tuple(Container(t), t) = GetLast[] + + Unshift(Element:t):Container(t) = AddFirst(Element) + Shift():tuple(Container(t), t) = GetFirst[] + } +} \ No newline at end of file diff --git a/vendor/README.md b/vendor/README.md index 919ebefcbe..596eeae532 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -665,6 +665,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Velocity Template Language:** [animecyc/AtomLanguageVelocity](https://github.com/animecyc/AtomLanguageVelocity) - **Vento:** [ventojs/vscode-vento](https://github.com/ventojs/vscode-vento) - **Verilog:** [textmate/verilog.tmbundle](https://github.com/textmate/verilog.tmbundle) +- **Verse:** [simnJS/verse-grammar](https://github.com/simnJS/verse-grammar) - **Vim Help File:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) - **Vim Script:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) - **Vim Snippet:** [Alhadis/language-viml](https://github.com/Alhadis/language-viml) diff --git a/vendor/grammars/verse-grammar b/vendor/grammars/verse-grammar new file mode 160000 index 0000000000..3abf18ec6e --- /dev/null +++ b/vendor/grammars/verse-grammar @@ -0,0 +1 @@ +Subproject commit 3abf18ec6e8d9fcbbb15be8183cb5b2cb6a621b8 diff --git a/vendor/licenses/git_submodule/verse-grammar.dep.yml b/vendor/licenses/git_submodule/verse-grammar.dep.yml new file mode 100644 index 0000000000..51327e3fe8 --- /dev/null +++ b/vendor/licenses/git_submodule/verse-grammar.dep.yml @@ -0,0 +1,33 @@ +--- +name: verse-grammar +version: 3abf18ec6e8d9fcbbb15be8183cb5b2cb6a621b8 +type: git_submodule +homepage: https://github.com/simnjs/verse-grammar.git +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2025 GAY Simon + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: MIT License - see LICENSE file for details. +notices: [] From dba0d4d63cf8b2ab53e5fd620062867cbe39f422 Mon Sep 17 00:00:00 2001 From: simnJS Date: Tue, 12 May 2026 13:33:42 +0000 Subject: [PATCH 2/2] Replace VerseFeatures.verse with smaller Hexadecimal.verse --- samples/Verse/Hexadecimal.verse | 126 +++++++++ samples/Verse/VerseFeatures.verse | 410 ------------------------------ 2 files changed, 126 insertions(+), 410 deletions(-) create mode 100644 samples/Verse/Hexadecimal.verse delete mode 100644 samples/Verse/VerseFeatures.verse diff --git a/samples/Verse/Hexadecimal.verse b/samples/Verse/Hexadecimal.verse new file mode 100644 index 0000000000..a6b6363bd8 --- /dev/null +++ b/samples/Verse/Hexadecimal.verse @@ -0,0 +1,126 @@ +Hexadecimal := module{ + using. BitMath + using. MathFeatures + using. StringProcessing + + UpperCaseHexAlphabet : string = "0123456789ABCDEF" + LowerCaseHexAlphabet : string = "0123456789abcdef" + + ReverseHexTable : [char]int = map{ + '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, + 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, + 'a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15 + } + + # Converts a single byte (0-255) to its two-character hexadecimal representation + # By default uses uppercase letters, but can use lowercase if wanted (for ex in MD5 Hashes) + # Fails if the input is outside the byte range + EncodeByteToHex(Byte:int, ?LowerCase:logic=false):string = { + HexAlphabet := LowerCase? and LowerCaseHexAlphabet or UpperCaseHexAlphabet + + HighNibble := HexAlphabet[Quotient[Byte, 16]] + LowNibble := HexAlphabet[Mod[Byte, 16]] + + "{HighNibble}{LowNibble}" + } + + # Converts a byte array to its hexadecimal string representation + EncodeByteArrayToHex(ByteArray:[]int, ?LowerCase:logic=false):string = { + HexParts := for (Byte : ByteArray) { + EncodeByteToHex[Byte, ?LowerCase := LowerCase] + } + Concatenate(HexParts) + } + + # WIP: Deprecate later (redundant) + # Converts a single char to its two-character hexadecimal representation + EncodeCharToHex(Character:char, ?LowerCase:logic=false):string = { + ByteValue := CharToByte(Character) + + EncodeByteToHex[ByteValue, ?LowerCase := LowerCase] or Err("Unreachable") + } + + # WIP: Deprecate later (redundant) + # Converts a string to its hexadecimal string representation + EncodeStringToHex(String:string, ?LowerCase:logic=false):string = { + HexParts := for (Character : String) { + EncodeCharToHex(Character, ?LowerCase := LowerCase) + } + Concatenate(HexParts) + } + + # WIP: Deprecate later (redundant) + # Converts an arbitrary-sized integer to its hexadecimal string representation + # Fails if the input integer is negative + EncodeIntToHex(Value:int, ?LowerCase:logic=false):string = { + Value >= 0 + + # Calculate how many bytes are needed to represent the unsigned value + ByteCount := Value = 0 and 1 or Quotient[GetBitWidth(Value) + 8, 8] or Err("Unreachable") + + # 2. Iterate through the bytes from high to low + Concatenate(for (Index := 1..ByteCount) { + # We want to print Big-Endian (Most Significant Byte first) + # So we invert the index + BytePosition := ByteCount - Index + + # Calculate divisor: 2^(BytePosition * 8) + Divisor := PowerOfTwo[BytePosition * 8] or Err("Unreachable") + + # Shift the value down to this byte position + # This gives us the value of this byte AND all bytes above it + ShiftedValue := Quotient[Value, Divisor] + + # If ShiftedValue is 0, it means this byte (and all above it) are 0. + # We skip it (return empty string) UNLESS it's the very last byte (Divisor=1). + # This ensures '0' prints as "00" but '0xFF' prints as "FF" (not "00FF"). + (ShiftedValue > 0 or Divisor = 1) and ( + ByteValue := Mod[ShiftedValue, 256] or Err("Unreachable") + EncodeByteToHex[ByteValue, ?LowerCase := LowerCase] or Err("Unreachable") + ) or "" + }) + } + + # Converts a hexadecimal string into its respective byte array + # Fails if the input string has an odd length or contains invalid hexadecimal characters + DecodeHexToByteArray(HexString:string):[]int = { + HexLength := HexString.Length + + Mod[HexLength, 2] = 0 + + for (Index := 0..(Truncate(HexLength / 2)) - 1) { + HighChar := HexString[Index * 2] or Err("Unreachable") + LowChar := HexString[(Index * 2) + 1] or Err("Unreachable") + + HighValue := ReverseHexTable[HighChar] + LowValue := ReverseHexTable[LowChar] + + (HighValue * 16) + LowValue + } + } + + # WIP: Deprecate later (redundant) + # Decodes a hexadecimal string into its normal byte string representation + # Fails if the input string has an odd length or contains invalid hexadecimal characters + DecodeHexToString(HexString:string):string = { + ByteArray := DecodeHexToByteArray[HexString] + ByteArrayToString[ByteArray] or Err("Unreachable") + } + + # WIP: Deprecate later (redundant) + # Decodes a hexadecimal string into its integer representation + # Fails if the input string is empty, has an odd length, or contains invalid hexadecimal characters + DecodeHexToInt(HexString:string):int = { + var Result : int = 0 + + ByteArray := DecodeHexToByteArray[HexString] + + ByteArray.Length > 0 + + for (Byte : ByteArray) { + set Result = (Result * 256) + Byte + } + + Result + } +} \ No newline at end of file diff --git a/samples/Verse/VerseFeatures.verse b/samples/Verse/VerseFeatures.verse deleted file mode 100644 index 08a60e4f40..0000000000 --- a/samples/Verse/VerseFeatures.verse +++ /dev/null @@ -1,410 +0,0 @@ -using. /Fortnite.com/Characters -using. /Fortnite.com/Devices -using. /UnrealEngine.com/Temporary -using. /Verse.org/Random -using. /Verse.org/Simulation - -using. Wrappers - -VerseFeatures := module { - - ### Runtime Utils - - SleepTicks(Count:int):void = for (X := 1..Count). Sleep(0.0) - - ### Logic Utils - - Nothing():void = void - Success():void = void - Failure():false = false? and Err("Unreachable") - - # Unsafe function call, it expect to always succeed, or else will throw a runtime error. - # - # It was made for usage on places where the result is already known to always succeed, such - # as defining constants and avoiding patterns like `Something[] or Err("Unreachable")` - Must(Function:type{_(:t):t2}, Args:t where t:type, t2:type):t2 = { - Function[Args] or Err("This is your fault.") - } - Must(Result:result(t, any) where t:type):t = { - Result.GetSuccess[] or Err("This is your fault.") - } - - (VerseFeatures:)ToString(Input:logic):string = { - if (Input?) { "true" } else { "false" } - } - - CoinFlip():logic = logic{GetRandomInt(0, 1) = 1} - - (Logic:logic).Invert():logic = logic{not Logic?} - - prefix'-'(Logic:logic):logic = Logic.Invert() - - - - ### Agent Utils - - IsRealPlayer(Player:agent):player = player[Player] - - (Agent:agent).IsValid():void = { - player[Agent].IsActive[] or not player[Agent] - Agent.GetFortCharacter[].IsActive[] - } - - (Agent:agent).AwaitValidation(?MaxTimeout:float=Inf, ?RefreshInterval:float=0.1):logic = { - CallbackEvent := event(agent){} - branch { - loop { - if (Agent.IsValid[]). CallbackEvent.Signal(Agent) - Sleep(Max(0.0, RefreshInterval)) - } - } - - CallbackEvent.AwaitFor(Agent, ?MaxTimeout := MaxTimeout) - } - - - - ### Message Utils - - JoinMessages(First:message, Second:message):message = "{First}{Second}" - - operator'+'(First:message, Second:message):message = "{First}{Second}" - - - - ### Map Operations - - # Returns an array with all keys of the map - (Input:[k]v where k:subtype(comparable), v:type).Keys():[]k = { - for (CurrentKey->_Unused : Input). CurrentKey - } - - # Returns an array with all values of the map - (Input:[k]v where k:subtype(comparable), v:type).Values():[]v = { - for (CurrentValue : Input). CurrentValue - } - - # Merges an array of maps into a single map. - # In case of key conflicts, later maps override earlier ones. - ReduceMaps(InputMaps:[][k]v where k:subtype(comparable), v:type):[k]v = { - InputMapsLength := InputMaps.Length - - FirstMap := InputMaps[0] or (return map{}) - SecondMap := InputMaps[1] or (return FirstMap) - - MergedMap := ConcatenateMaps(FirstMap, SecondMap) - - OtherMaps := InputMaps.Slice[2] or Err("Unreachable") - - if (OtherMaps.Length = 0). return MergedMap - - return ReduceMaps(array{MergedMap} + OtherMaps) - } - - # Returns a map without the map element that Key matches the Key provided - # If Strict is set to true, will force a fail when key does not exist on the map - (Input:[k]v).RemoveByKey(KeyToRemove:k, (?Strict:logic=false) where k:subtype(comparable), v:type):[k]v = { - if (Strict?). Input[KeyToRemove] - - var TempMap:[k]v = map{} - for ( - CurrentKey->CurrentValue:Input - CurrentKey <> KeyToRemove - ). set TempMap[CurrentKey] = CurrentValue - TempMap - } - - # Returns a map without all map elements that Key matches the Keys provided - (Input:[k]v).RemoveByKeys(KeysToRemove:[]k where k:subtype(comparable), v:type):[k]v = { - var TempMap:[k]v = Input - for (KeyToRemove : KeysToRemove) { - ResultMap := TempMap.RemoveByKey[KeyToRemove, ?Strict := false] # Strict default value is already false on the function definition and due to that is not a required argument, but if not mentioned again here, for some reason it crashes the server - set TempMap = ResultMap - } - TempMap - } - - # Returns a map with only the elements that Key matches the Keys provided - (Input:[k]v).FilterByKeys(Keys:[]k where k:subtype(comparable), v:type):[k]v = { - var TempMap:[k]v = map{} - for ( - CurrentKey->CurrentValue:Input - KeyToCompare : Keys - CurrentKey = KeyToCompare - ). set TempMap[CurrentKey] = CurrentValue - TempMap - } - - # Returns the first map Key where the associated Value matches the provided Value - (Input:[k]v).FindKeyByValue(Value:v where k:subtype(comparable), v:subtype(comparable)):k = { - var MaybeKey:?k = false - for ( - CurrentKey->CurrentValue:Input - not MaybeKey? - CurrentValue = Value - ). set MaybeKey = option{CurrentKey} - MaybeKey? - } - - # Returns all map Keys where the associated Value matches the provided Value - # If Strict is set to true, will force a fail when none of the values exist on the map - (Input:[k]v).FindKeysByValue(Value:v, (?Strict:logic=false) where k:subtype(comparable), v:subtype(comparable)):[]k = { - KeysFound := for ( - CurrentKey->CurrentValue:Input - CurrentValue = Value - ). CurrentKey - - KeysFound.Length > 0 or not Strict? - - KeysFound - } - - # Return a map with only the elements that Value matches the Values provided - # If Strict is set to true, will force a fail when none of the values exist on the map - (Input:[k]v).FilterByValues(Values:[]v, (?Strict:logic=false) where k:subtype(comparable), v:subtype(comparable)):[k]v = { - var TempMap:[k]v = map{} - for ( - CurrentKey->CurrentValue:Input - ValueToCompare : Values - CurrentValue = ValueToCompare - ). set TempMap[CurrentKey] = CurrentValue - - TempMap.Length > 0 or not Strict? - - TempMap - } - - # Returns the first map Key where the Value succeeds the provided condition function - (Input:[k]v).FindKeyByValueData(Condition:type{_(:v):void} where k:subtype(comparable), v:type):k = { - var MaybeKey:?k = false - for ( - CurrentKey->CurrentValue:Input - not MaybeKey? - Condition[CurrentValue] - ). set MaybeKey = option{CurrentKey} - MaybeKey? - } - - # Returns all map Keys where the Value succeeds the provided condition function - # If Strict is set to true, will force a fail when no compatible values exist on the map - (Input:[k]v).FindKeysByValueData(Condition:type{_(:v):void}<#, (?Strict:logic=false)#> where k:subtype(comparable), v:type):[]k = { - KeysFound := for ( - CurrentKey->CurrentValue:Input - Condition[CurrentValue] - ). CurrentKey - - <#KeysFound.Length > 0 or not Strict?#> - - KeysFound - } - - - - ### Array Operations - - <#> NOTE: Some functions bellow are written to be easy to understand, not necessarily optimized for performance. - Many array operations makes copies of the entire array even when is not needed, and some iterations does - not have early breaks/exists, making it less efficient due to always iterating over all array values. - - (Input:[]t where t:type).Reverse():[]t = { - LastIndex := Input.Length - 1 - for ( - CurrentIndex->_Unused : Input - Element := Input[LastIndex - CurrentIndex] - ). Element - } - - NormalizeIndex(Length:int, Index:int):int = { - Length >= 1 - - NormalizedRange := Mod[Index, Length] - - NormalizedIndex := if (NormalizedRange < 0). Length + NormalizedRange - else. NormalizedRange - } - - (Input:[]t where t:type).At(Index:int):t = { - Input[NormalizeIndex[Input.Length, Index]] - } - - (Input:[]t where t:subtype(comparable)).FindLast(ElementToFind:t):int = { - LastIndex := Input.Length - 1 - - var MaybeIndex:?int = false - for ( - Index := 0..LastIndex - not MaybeIndex? - InverseIndex := LastIndex - Index - Input[InverseIndex] = ElementToFind - ). set MaybeIndex = option{InverseIndex} - - MaybeIndex? - } - - (Input:generator(t) where t:type).AsArray():[]t = { - for (Element : Input). Element - } - - (Input:generator(t) where t:type).GetFirst():t = { - Input.AsArray()[0] - } - - # Return the Index of the first Element on the array that succeeds the provided condition function - (Input:[]t).FindBy(Condition:type{_(:t):void} where t:type):int = { - var MaybeIndex:?int = false - for ( - Index->Element:Input - not MaybeIndex? - Condition[Element] - ). set MaybeIndex = option{Index} - MaybeIndex? - } - - # Return the Index of the first Element on the array that succeeds the provided condition function with additional arguments - (Input:[]t).FindByDynamic(Condition:type{_(:t, :t2):void}, ConditionArgs:t2 where t:type, t2:type):int = { - var MaybeIndex:?int = false - for ( - Index->Element:Input - not MaybeIndex? - Condition[Element, ConditionArgs] - ). set MaybeIndex = option{Index} - MaybeIndex? - } - - # Return an array with only the Elements that succeeds the provided condition function - # If Strict is set to true, will force a fail when no compatible elements exist on the array - (Input:[]t).FilterBy(Condition:type{_(:t):void}, (?Strict:logic=false) where t:type):[]t = { - NewArray := for ( - CurrentValue:Input - Condition[CurrentValue] - ). CurrentValue - - NewArray.Length > 0 or not Strict? - - NewArray - } - - # Return an array with only the Elements that succeeds the provided condition function with additional arguments - # If Strict is set to true, will force a fail when no compatible elements exist on the array - (Input:[]t).FilterByDynamic(Condition:type{_(:t, :t2):void}, ConditionArgs:t2, (?Strict:logic=false) where t:type, t2:type):[]t = { - NewArray := for ( - CurrentValue:Input - Condition[CurrentValue, ConditionArgs] - ). CurrentValue - - NewArray.Length > 0 or not Strict? - - NewArray - } - - - - ### Primitive Math Sorting Operations - - SortIntAscending(Left:int, Right:int):void = { - Left < Right - } - - SortIntDescending(Left:int, Right:int):void = { - not SortIntAscending[Left, Right] - } - - SortFloatAscending(Left:float, Right:float):void = { - Left < Right - } - - SortFloatDescending(Left:float, Right:float):void = { - not SortFloatAscending[Left, Right] - } - - - - ### Containers - - # Generic container class that can be used for FIFO/LIFO/Linked Lists and other common types of array data storage implementations - <#> NOTE: due to current verse limitations, we can't have mutable members (variables) in parametric classes, and for - that reason we need to handle and keep track of the container state changes outside of this class during usage. - Container(t:type) := class { - Elements :[]t = array{} - - PointerPosition:int = 0 - - Size():int = Elements.Length - IsEmpty():void = Elements.Length = 0 - IsPointerValid(?Position:int = PointerPosition):void = Position >= 0 and Position < Elements.Length # Should only fail if has 0 elements due to the class automatically sanitizing the pointer position - - SetPointerPosition(Position:int, ?Circular:logic = false):Container(t) = Container(t){ - PointerPosition := if (Circular?) { - NormalizeIndex[Elements.Length, Position] - } else { - IsPointerValid[?Position := Position] - Position - } - Elements := Elements - } - SetPointerOffset(Offset:int, ?Circular:logic = false):Container(t) = SetPointerPosition[PointerPosition + Offset, ?Circular := Circular] - - AddFirst(Element:t):Container(t) = Container(t){ - PointerPosition := PointerPosition + 1 - Elements := array{Element} + Elements - } - AddLast(Element:t):Container(t) = Container(t){ - PointerPosition := PointerPosition - Elements := Elements + array{Element} - } - - # Get the value at an specific index - # Fails if not able to find a value on the provided index - # If Strict is set to false (Default), out of bound indexes will be normalized to array bounds - # If Strict is set to true, out of bound indexes will not be normalized and will cause a fail - GetAtIndex(Index:int, ?Strict:logic=false):tuple(Container(t), t) = { - TargetIndex := if (Strict?). Index else. NormalizeIndex[Elements.Length, Index] - - (Container(t){ - PointerPosition := if (PointerPosition < TargetIndex). PointerPosition else. Max(0, Min(PointerPosition - 1, Elements.Length - 2)) - Elements := Elements.RemoveElement[TargetIndex] - }, Elements[TargetIndex]) - } - - GetAtPointer():t = Elements[PointerPosition] - - GetFirst():tuple(Container(t), t) = ( - Container(t){ - PointerPosition := Max(PointerPosition - 1, 0) - Elements := Elements.RemoveElement[0] - }, - Elements[0] - ) - - GetLast():tuple(Container(t), t) = ( - Container(t){ - PointerPosition := Max(0, Min(PointerPosition, Elements.Length - 1)) - Elements := Elements.RemoveElement[Elements.Length - 1] - }, - Elements[Elements.Length - 1] - ) - - ### WIP Future Planned Features: - # AddAt - # Find - # FindAll - # Reduce ? - # Splice - - # `(_Self:Container(t))` is a fix for (local:) not being allowed in class scope - (_Self:Container(t)).SortBy(Less:type {_(:t, :t):void}):Container(t) = Container(t){ - PointerPosition := PointerPosition - Elements := SortBy(Elements, Less) - } - - # Aliases - PreviousPointer(?Circular:logic = false):Container(t) = SetPointerOffset[-1, ?Circular := Circular] - NextPointer(?Circular:logic = false):Container(t) = SetPointerOffset[1, ?Circular := Circular] - - Push(Element:t):Container(t) = AddLast(Element) - Pop():tuple(Container(t), t) = GetLast[] - - Unshift(Element:t):Container(t) = AddFirst(Element) - Shift():tuple(Container(t), t) = GetFirst[] - } -} \ No newline at end of file