Skip to content

🔢 Mini-language/DSL for defining and dealing with ranges of numbers

License

Notifications You must be signed in to change notification settings

xyproto/rangetype

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rangetype

Build Status GoDoc License Report Card

A mini-language for defining numeric types by defining ranges.

The idea is to provide a DSL for defining and validating numeric types for implementations of programming languages.

It can also be used for iterating over ranges, generating lists of numbers or slicing a given slice (like slices in Python).

ForEach

Looping over a range can be done by providing a function that takes a float64:

r.New("1..10").ForEach(func(x float64) {
	fmt.Println(int(x))
})

Join

Collecting integers to a comma separated string can be done with Join:

r.New("1..10").Join(", ", 0)

Or for floats, with 2 digits after the period, separated by semicolons:

r.New("1..3 step 0.5").Join(";", 2)

Syntax

Expressions can optionally start with:

  • [ for including the first value in the range, or
  • ( for excluding the first value in the range

And can end with:

  • ] for including the last value in the range, or
  • ) for excluding the last value in the range

Numbers can be suffixed by:

  • ~ for subtracting 1 from the preceding number. Any number of ~ is possible.

The ranges can be Python-style:

[0:10]

Python-style with a step:

[1:20:-1]

Ruby-style:

1..10

Ruby-style with a step:

1..10 step 2

Math-style:

[1,5)

Math-style with a step:

[1,5) step 2

Math-style with negative steps:

(5,1] step -0.1

Here's an expression for specifying the range for a 16-bit unsigned integer:

0..65535

This can also be defined by dropping the initial 0 and using ~ for -1:

..65536~

This can be made clearer (when defining many numbers of many bit-sizes) by using 2**16 instead of 65536:

..2**16~

This can be used for validating if a given number fits a 16-bit unsigned type:

IntType := r.New("..2**16~")     // from 0 up to and including 65536-1
IntType.Valid(42)              // true

Examples

An int with a range from 1 to 3 that includes both 1, 2 and 3:

1,3

A float with a range from 1 to 3 that includes 1.0, 1.1 etc up to 3.0:

1,3 step 0.1

Inclusivity is specified with square brackets:

[1,3]

Exclusivity is specified with parenthesis:

(1,3)

Ranges inspired by Ruby also work:

1..3

These are inclusive, unless parenthesis are used.

Ruby-style range which will exclude 1 and 3 and only keep 2:

(1..3)

Python style ranges are also supported, where the start value is inclusive and the end value is exclusive:

1:3

Adding square brackets makes the range inclusive:

[1:3]

Brackets and parenthesis does not have to be balanced. This works too:

1:3]

Adding an iteration step is also possible:

1..5 step 2

This is a range with the numbers 1, 3 and 5.

The Python-style syntax also supports steps:

[3:1:-1]

This is 3, 2, 1.

Steps does not have to be integers:

[3:1:-0.1]

This steps from 3 (inclusive) down to 1 (inclusive) in step sizes of 0.1.

More Examples

Defining a SmallInt type and checking if a given number is valid

package main

import (
	"fmt"

	r "github.com/xyproto/rangetype"
)

func main() {
	// Define a new type that can hold numbers from 0 up to and including 99
	SmallInt := r.New("0..99")

	// Another way to define a number type from 0 up to and excluding 100
	//SmallInt := r.New("[0,100)")

	// Another way to define a number type from 0 up to and excluding 100
	//SmallInt := r.New("..10**2~")

	// Is 42 a valid SmallInt?
	fmt.Println("0 is a valid SmallInt value:", SmallInt.Valid(0))
	fmt.Println("2 is a valid SmallInt value:", SmallInt.Valid(2))
	fmt.Println("-1 is a valid SmallInt value:", SmallInt.Valid(-1))
	fmt.Println("99 is a valid SmallInt value:", SmallInt.Valid(99))
	fmt.Println("100 is a valid SmallInt value:", SmallInt.Valid(100))

	// How many integers are there room for?
	fmt.Printf("SmallInt can hold %d different numbers.\n", SmallInt.Len())
	fmt.Printf("Storage required for SmallInt: a %d-bit int\n", SmallInt.Bits())

	// All possible SmallInt values, comma separated:
	fmt.Println("All possible values for SmallInt:\n" + SmallInt.Join(",", 0))
}

Example 2

Slicing slices and looping with the ForEach method

package main

import (
	"fmt"

	r "github.com/xyproto/rangetype"
)

func main() {
	// Outputs 1 to 10, with 0 digits after "."
	fmt.Println(r.New("1..10").Join(", ", 0))

	// Outputs 2 and 4
	for _, x := range r.Slice([]float64{1.0, 2.0, 3.0, 4.0}, "1..3 step 2") {
		fmt.Print(x, " ")
	}
	fmt.Println()

	// Also outputs 2 and 4
	r.New("(0:6:2)").ForEach(func(x float64) {
		fmt.Print(x, " ")
	})
	fmt.Println()
}

There are more examples in the range_test.go file.

Features and Limitations

  • Can handle very large ranges without storing the actual numbers in the ranges, but iterating over large ranges may be slow.
  • Only ** and ~ are supported for manipulating numbers in the range expressions.
  • It's not a general language, it's only a DSL for expressing ranges of integers or floating point numbers, with an optional step size.

Error Handling

  • New2 and Slice2 will return both a value and an error (if the expression failed to evaluate) and are the recommended functions to use.
  • New and Slice are fine to use for expressions that are already known to evaluate, but may panic if there are errors in the given expression.

General Info

About

🔢 Mini-language/DSL for defining and dealing with ranges of numbers

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published