goscript 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2024-2025 Aperture Robotics, LLC.
4
- Copyright (c) 2019-2025 Christian Stewart
4
+ Copyright (c) 2019-2025 Christian Stewart <christian@cjs.zip>
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # GoScript
2
2
 
3
- [![GoDoc Widget]][GoDoc] [![Go Report Card Widget]][Go Report Card]
3
+ [![GoDoc Widget]][GoDoc] [![Go Report Card Widget]][Go Report Card] [![DeepWiki Widget]][DeepWiki]
4
4
 
5
5
  [GoDoc]: https://godoc.org/github.com/aperturerobotics/goscript
6
6
  [GoDoc Widget]: https://godoc.org/github.com/aperturerobotics/goscript?status.svg
7
7
  [Go Report Card Widget]: https://goreportcard.com/badge/github.com/aperturerobotics/goscript
8
8
  [Go Report Card]: https://goreportcard.com/report/github.com/aperturerobotics/goscript
9
+ [DeepWiki Widget]: https://img.shields.io/badge/DeepWiki-aperturerobotics%2Fgoscript-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==
10
+ [DeepWiki]: https://deepwiki.com/aperturerobotics/goscript
9
11
 
10
12
  ## Introduction
11
13
 
@@ -731,8 +731,7 @@ export interface StructTypeInfo extends BaseTypeInfo {
731
731
  kind: TypeKind.Struct
732
732
  methods: Set<string>
733
733
  ctor?: new (...args: any[]) => any
734
- fields?: Set<string> // Field names for struct types
735
- fieldTypes?: Record<string, TypeInfo | string> // Types for struct fields
734
+ fields: Record<string, TypeInfo | string> // Field names and types for struct fields
736
735
  }
737
736
 
738
737
  /**
@@ -870,13 +869,15 @@ export const registerStructType = (
870
869
  zeroValue: any,
871
870
  methods: Set<string>,
872
871
  ctor: new (...args: any[]) => any,
872
+ fields: Record<string, TypeInfo | string> = {},
873
873
  ): StructTypeInfo => {
874
874
  const typeInfo: StructTypeInfo = {
875
875
  name,
876
876
  kind: TypeKind.Struct,
877
877
  zeroValue,
878
878
  methods,
879
- ctor: ctor,
879
+ ctor,
880
+ fields,
880
881
  }
881
882
  typeRegistry.set(name, typeInfo)
882
883
  return typeInfo
@@ -992,24 +993,35 @@ function matchesStructType(value: any, info: TypeInfo): boolean {
992
993
  return true
993
994
  }
994
995
 
995
- if (info.fields && typeof value === 'object') {
996
- // For struct type assertions, we need to check that the value has exactly
997
- const descFields = Array.from(info.fields as Set<string>)
996
+ if (info.methods && typeof value === 'object' && value !== null) {
997
+ const allMethodsMatch = Array.from(info.methods).every(
998
+ (method) => typeof value[method] === 'function',
999
+ )
1000
+ if (allMethodsMatch) {
1001
+ return true
1002
+ }
1003
+ }
1004
+
1005
+ if (typeof value === 'object' && value !== null) {
1006
+ const fieldNames = Object.keys(info.fields || {})
998
1007
  const valueFields = Object.keys(value)
999
1008
 
1000
- const condition1 = descFields.every((field) => field in value)
1001
- const condition2 = valueFields.length === descFields.length
1002
- const condition3 = valueFields.every((field) => descFields.includes(field))
1009
+ const fieldsExist = fieldNames.every((field) => field in value)
1010
+ const sameFieldCount = valueFields.length === fieldNames.length
1011
+ const allFieldsInStruct = valueFields.every((field) =>
1012
+ fieldNames.includes(field),
1013
+ )
1003
1014
 
1004
- console.log('Struct field matching debug:', {
1005
- descFields,
1006
- valueFields,
1007
- allDescFieldsInValue: condition1,
1008
- sameFieldCount: condition2,
1009
- allValueFieldsInDesc: condition3,
1010
- })
1015
+ if (fieldsExist && sameFieldCount && allFieldsInStruct) {
1016
+ return Object.entries(info.fields).every(([fieldName, fieldType]) => {
1017
+ return matchesType(
1018
+ value[fieldName],
1019
+ normalizeTypeInfo(fieldType as TypeInfo | string),
1020
+ )
1021
+ })
1022
+ }
1011
1023
 
1012
- return condition1 && condition2 && condition3
1024
+ return false
1013
1025
  }
1014
1026
 
1015
1027
  return false
@@ -1126,9 +1138,10 @@ function matchesArrayOrSliceType(value: any, info: TypeInfo): boolean {
1126
1138
  * @returns True if the value matches the pointer type, false otherwise.
1127
1139
  */
1128
1140
  function matchesPointerType(value: any, info: TypeInfo): boolean {
1129
- // For pointers, check if value is a Box (has a 'value' property)
1141
+ // Allow null/undefined values to match pointer types to support nil pointer assertions
1142
+ // This enables Go's nil pointer type assertions like `ptr, ok := i.(*SomeType)` to work correctly
1130
1143
  if (value === null || value === undefined) {
1131
- return false
1144
+ return true
1132
1145
  }
1133
1146
 
1134
1147
  // Check if the value is a Box (has a 'value' property)
@@ -1153,9 +1166,18 @@ function matchesPointerType(value: any, info: TypeInfo): boolean {
1153
1166
  * @param info The function type info to match against.
1154
1167
  * @returns True if the value matches the function type, false otherwise.
1155
1168
  */
1156
- function matchesFunctionType(value: any, info: TypeInfo): boolean {
1157
- // For functions, check if the value is a function
1158
- return typeof value === 'function'
1169
+ function matchesFunctionType(value: any, info: FunctionTypeInfo): boolean {
1170
+ // First check if the value is a function
1171
+ if (typeof value !== 'function') {
1172
+ return false;
1173
+ }
1174
+
1175
+ // This is important for named function types
1176
+ if (info.name && value.__goTypeName) {
1177
+ return info.name === value.__goTypeName;
1178
+ }
1179
+
1180
+ return true;
1159
1181
  }
1160
1182
 
1161
1183
  /**
@@ -1165,17 +1187,56 @@ function matchesFunctionType(value: any, info: TypeInfo): boolean {
1165
1187
  * @param info The channel type info to match against.
1166
1188
  * @returns True if the value matches the channel type, false otherwise.
1167
1189
  */
1168
- function matchesChannelType(value: any, info: TypeInfo): boolean {
1169
- return (
1170
- typeof value === 'object' &&
1171
- value !== null &&
1172
- 'send' in value &&
1173
- 'receive' in value &&
1174
- 'close' in value &&
1175
- typeof value.send === 'function' &&
1176
- typeof value.receive === 'function' &&
1177
- typeof value.close === 'function'
1178
- )
1190
+ function matchesChannelType(value: any, info: ChannelTypeInfo): boolean {
1191
+ // First check if it's a channel or channel reference
1192
+ if (typeof value !== 'object' || value === null) {
1193
+ return false
1194
+ }
1195
+
1196
+ // If it's a ChannelRef, get the underlying channel
1197
+ let channel = value
1198
+ let valueDirection = 'both'
1199
+
1200
+ if ('channel' in value && 'direction' in value) {
1201
+ channel = value.channel
1202
+ valueDirection = value.direction
1203
+ }
1204
+
1205
+ // Check if it has channel methods
1206
+ if (
1207
+ !('send' in channel) ||
1208
+ !('receive' in channel) ||
1209
+ !('close' in channel) ||
1210
+ typeof channel.send !== 'function' ||
1211
+ typeof channel.receive !== 'function' ||
1212
+ typeof channel.close !== 'function'
1213
+ ) {
1214
+ return false
1215
+ }
1216
+
1217
+ if (info.elemType) {
1218
+ if (
1219
+ info.elemType === 'string' &&
1220
+ 'zeroValue' in channel &&
1221
+ channel.zeroValue !== ''
1222
+ ) {
1223
+ return false
1224
+ }
1225
+
1226
+ if (
1227
+ info.elemType === 'number' &&
1228
+ 'zeroValue' in channel &&
1229
+ typeof channel.zeroValue !== 'number'
1230
+ ) {
1231
+ return false
1232
+ }
1233
+ }
1234
+
1235
+ if (info.direction) {
1236
+ return valueDirection === info.direction
1237
+ }
1238
+
1239
+ return true
1179
1240
  }
1180
1241
 
1181
1242
  /**
@@ -1211,7 +1272,7 @@ function matchesType(value: any, info: TypeInfo): boolean {
1211
1272
  return matchesPointerType(value, info)
1212
1273
 
1213
1274
  case TypeKind.Function:
1214
- return matchesFunctionType(value, info)
1275
+ return matchesFunctionType(value, info as FunctionTypeInfo)
1215
1276
 
1216
1277
  case TypeKind.Channel:
1217
1278
  return matchesChannelType(value, info)
@@ -1230,23 +1291,57 @@ export function typeAssert<T>(
1230
1291
  ): TypeAssertResult<T> {
1231
1292
  const normalizedType = normalizeTypeInfo(typeInfo)
1232
1293
 
1294
+ if (isPointerTypeInfo(normalizedType) && value === null) {
1295
+ return { value: null as unknown as T, ok: true }
1296
+ }
1297
+
1298
+ if (
1299
+ isStructTypeInfo(normalizedType) &&
1300
+ normalizedType.methods &&
1301
+ typeof value === 'object' &&
1302
+ value !== null
1303
+ ) {
1304
+ // Check if the value implements all methods of the struct type
1305
+ const allMethodsMatch = Array.from(normalizedType.methods).every(
1306
+ (method) => typeof value[method] === 'function',
1307
+ )
1308
+
1309
+ const hasAnyMethod = Array.from(normalizedType.methods).some(
1310
+ (method) => typeof value[method] === 'function',
1311
+ )
1312
+
1313
+ if (allMethodsMatch && hasAnyMethod && normalizedType.methods.size > 0) {
1314
+ // For interface-to-concrete type assertions, we just need to check methods
1315
+ return { value: value as T, ok: true }
1316
+ }
1317
+ }
1318
+
1233
1319
  if (
1234
1320
  isStructTypeInfo(normalizedType) &&
1235
1321
  normalizedType.fields &&
1236
1322
  typeof value === 'object' &&
1237
1323
  value !== null
1238
1324
  ) {
1239
- const descFields = Array.from(normalizedType.fields as Set<string>)
1325
+ const fieldNames = Object.keys(normalizedType.fields)
1240
1326
  const valueFields = Object.keys(value)
1241
1327
 
1242
1328
  // For struct type assertions, we need exact field matching
1243
- const structMatch =
1244
- descFields.length === valueFields.length &&
1245
- descFields.every((field: string) => field in value) &&
1246
- valueFields.every((field) => descFields.includes(field))
1329
+ const structFieldsMatch =
1330
+ fieldNames.length === valueFields.length &&
1331
+ fieldNames.every((field: string) => field in value) &&
1332
+ valueFields.every((field) => fieldNames.includes(field))
1333
+
1334
+ if (structFieldsMatch) {
1335
+ const typesMatch = Object.entries(normalizedType.fields).every(
1336
+ ([fieldName, fieldType]) => {
1337
+ return matchesType(
1338
+ value[fieldName],
1339
+ normalizeTypeInfo(fieldType as TypeInfo | string),
1340
+ )
1341
+ },
1342
+ )
1247
1343
 
1248
- if (structMatch) {
1249
- return { value: value as T, ok: true }
1344
+ return { value: value as T, ok: typesMatch }
1250
1345
  } else {
1251
1346
  return { value: null as unknown as T, ok: false }
1252
1347
  }
@@ -1614,13 +1709,185 @@ class BufferedChannel<T> implements Channel<T> {
1614
1709
  }
1615
1710
  }
1616
1711
 
1712
+ /**
1713
+ * Represents a reference to a channel with a specific direction.
1714
+ */
1715
+ export interface ChannelRef<T> {
1716
+ /**
1717
+ * The underlying channel
1718
+ */
1719
+ channel: Channel<T>
1720
+
1721
+ /**
1722
+ * The direction of this channel reference
1723
+ */
1724
+ direction: 'send' | 'receive' | 'both'
1725
+
1726
+ // Channel methods
1727
+ send(value: T): Promise<void>
1728
+ receive(): Promise<T>
1729
+ receiveWithOk(): Promise<ChannelReceiveResult<T>>
1730
+ close(): void
1731
+ canSendNonBlocking(): boolean
1732
+ canReceiveNonBlocking(): boolean
1733
+ selectSend(value: T, id: number): Promise<SelectResult<boolean>>
1734
+ selectReceive(id: number): Promise<SelectResult<T>>
1735
+ }
1736
+
1737
+ /**
1738
+ * A bidirectional channel reference.
1739
+ */
1740
+ export class BidirectionalChannelRef<T> implements ChannelRef<T> {
1741
+ direction: 'both' = 'both'
1742
+
1743
+ constructor(public channel: Channel<T>) {}
1744
+
1745
+ // Delegate all methods to the underlying channel
1746
+ send(value: T): Promise<void> {
1747
+ return this.channel.send(value)
1748
+ }
1749
+
1750
+ receive(): Promise<T> {
1751
+ return this.channel.receive()
1752
+ }
1753
+
1754
+ receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1755
+ return this.channel.receiveWithOk()
1756
+ }
1757
+
1758
+ close(): void {
1759
+ this.channel.close()
1760
+ }
1761
+
1762
+ canSendNonBlocking(): boolean {
1763
+ return this.channel.canSendNonBlocking()
1764
+ }
1765
+
1766
+ canReceiveNonBlocking(): boolean {
1767
+ return this.channel.canReceiveNonBlocking()
1768
+ }
1769
+
1770
+ selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1771
+ return this.channel.selectSend(value, id)
1772
+ }
1773
+
1774
+ selectReceive(id: number): Promise<SelectResult<T>> {
1775
+ return this.channel.selectReceive(id)
1776
+ }
1777
+ }
1778
+
1779
+ /**
1780
+ * A send-only channel reference.
1781
+ */
1782
+ export class SendOnlyChannelRef<T> implements ChannelRef<T> {
1783
+ direction: 'send' = 'send'
1784
+
1785
+ constructor(public channel: Channel<T>) {}
1786
+
1787
+ // Allow send operations
1788
+ send(value: T): Promise<void> {
1789
+ return this.channel.send(value)
1790
+ }
1791
+
1792
+ // Allow close operations
1793
+ close(): void {
1794
+ this.channel.close()
1795
+ }
1796
+
1797
+ canSendNonBlocking(): boolean {
1798
+ return this.channel.canSendNonBlocking()
1799
+ }
1800
+
1801
+ selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1802
+ return this.channel.selectSend(value, id)
1803
+ }
1804
+
1805
+ // Disallow receive operations
1806
+ receive(): Promise<T> {
1807
+ throw new Error('Cannot receive from send-only channel')
1808
+ }
1809
+
1810
+ receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1811
+ throw new Error('Cannot receive from send-only channel')
1812
+ }
1813
+
1814
+ canReceiveNonBlocking(): boolean {
1815
+ return false
1816
+ }
1817
+
1818
+ selectReceive(id: number): Promise<SelectResult<T>> {
1819
+ throw new Error('Cannot receive from send-only channel')
1820
+ }
1821
+ }
1822
+
1823
+ /**
1824
+ * A receive-only channel reference.
1825
+ */
1826
+ export class ReceiveOnlyChannelRef<T> implements ChannelRef<T> {
1827
+ direction: 'receive' = 'receive'
1828
+
1829
+ constructor(public channel: Channel<T>) {}
1830
+
1831
+ // Allow receive operations
1832
+ receive(): Promise<T> {
1833
+ return this.channel.receive()
1834
+ }
1835
+
1836
+ receiveWithOk(): Promise<ChannelReceiveResult<T>> {
1837
+ return this.channel.receiveWithOk()
1838
+ }
1839
+
1840
+ canReceiveNonBlocking(): boolean {
1841
+ return this.channel.canReceiveNonBlocking()
1842
+ }
1843
+
1844
+ selectReceive(id: number): Promise<SelectResult<T>> {
1845
+ return this.channel.selectReceive(id)
1846
+ }
1847
+
1848
+ // Disallow send operations
1849
+ send(value: T): Promise<void> {
1850
+ throw new Error('Cannot send to receive-only channel')
1851
+ }
1852
+
1853
+ // Disallow close operations
1854
+ close(): void {
1855
+ throw new Error('Cannot close receive-only channel')
1856
+ }
1857
+
1858
+ canSendNonBlocking(): boolean {
1859
+ return false
1860
+ }
1861
+
1862
+ selectSend(value: T, id: number): Promise<SelectResult<boolean>> {
1863
+ throw new Error('Cannot send to receive-only channel')
1864
+ }
1865
+ }
1866
+
1867
+ /**
1868
+ * Creates a new channel reference with the specified direction.
1869
+ */
1870
+ export function makeChannelRef<T>(
1871
+ channel: Channel<T>,
1872
+ direction: 'send' | 'receive' | 'both',
1873
+ ): ChannelRef<T> {
1874
+ switch (direction) {
1875
+ case 'send':
1876
+ return new SendOnlyChannelRef<T>(channel)
1877
+ case 'receive':
1878
+ return new ReceiveOnlyChannelRef<T>(channel)
1879
+ default: // 'both'
1880
+ return new BidirectionalChannelRef<T>(channel)
1881
+ }
1882
+ }
1883
+
1617
1884
  /**
1618
1885
  * Represents a case in a select statement.
1619
1886
  */
1620
1887
  export interface SelectCase<T> {
1621
1888
  id: number
1622
1889
  isSend: boolean // true for send, false for receive
1623
- channel: Channel<any> | null // Allow null
1890
+ channel: Channel<any> | ChannelRef<any> | null // Allow null and ChannelRef
1624
1891
  value?: any // Value to send for send cases
1625
1892
  // Optional handlers for when this case is selected
1626
1893
  onSelected?: (result: SelectResult<T>) => Promise<void>
@@ -1736,13 +2003,24 @@ export async function selectStatement<T>(
1736
2003
  * Creates a new channel with the specified buffer size and zero value.
1737
2004
  * @param bufferSize The size of the channel buffer. If 0, creates an unbuffered channel.
1738
2005
  * @param zeroValue The zero value for the channel's element type.
1739
- * @returns A new channel instance.
2006
+ * @param direction Optional direction for the channel. Default is 'both' (bidirectional).
2007
+ * @returns A new channel instance or channel reference.
1740
2008
  */
1741
2009
  export const makeChannel = <T>(
1742
2010
  bufferSize: number,
1743
2011
  zeroValue: T,
1744
- ): Channel<T> => {
1745
- return new BufferedChannel<T>(bufferSize, zeroValue)
2012
+ direction: 'send' | 'receive' | 'both' = 'both',
2013
+ ): Channel<T> | ChannelRef<T> => {
2014
+ const channel = new BufferedChannel<T>(bufferSize, zeroValue)
2015
+
2016
+ // Wrap the channel with the appropriate ChannelRef based on direction
2017
+ if (direction === 'send') {
2018
+ return new SendOnlyChannelRef<T>(channel) as ChannelRef<T>
2019
+ } else if (direction === 'receive') {
2020
+ return new ReceiveOnlyChannelRef<T>(channel) as ChannelRef<T>
2021
+ } else {
2022
+ return channel
2023
+ }
1746
2024
  }
1747
2025
 
1748
2026
  /**