This commit is contained in:
parent
f6eb30e460
commit
031edd2ffe
9 changed files with 102 additions and 3 deletions
|
@ -13,6 +13,7 @@
|
||||||
* High performance (`ssa` and `asm` optimizations)
|
* High performance (`ssa` and `asm` optimizations)
|
||||||
* Tiny executables ("Hello World" is ~500 bytes)
|
* Tiny executables ("Hello World" is ~500 bytes)
|
||||||
* Fast compilation (5-10x faster than most)
|
* Fast compilation (5-10x faster than most)
|
||||||
|
* Unix scripting (JIT compilation)
|
||||||
* No dependencies (no llvm, no libc)
|
* No dependencies (no llvm, no libc)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -25,10 +26,41 @@ go build
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
Quick test:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
q run examples/hello
|
q run examples/hello
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Build an executable:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
q build examples/hello
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross-compile for another OS:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
q build examples/hello --os windows
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unix scripts
|
||||||
|
|
||||||
|
The compiler is actually so fast that it's possible to use `q` for scripting. Create a new file:
|
||||||
|
|
||||||
|
```q
|
||||||
|
#!/usr/bin/env q
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
main() {
|
||||||
|
io.write("Hello\n")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add permissions via `chmod +x`. The file can be executed from anywhere now.
|
||||||
|
The machine code is run directly from memory if the OS supports it.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
7
examples/script/script.q
Executable file
7
examples/script/script.q
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env q
|
||||||
|
|
||||||
|
import io
|
||||||
|
|
||||||
|
main() {
|
||||||
|
io.write("Hello\n")
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
// Exec runs the command included in the first argument and returns the exit code.
|
// Exec runs the command included in the first argument and returns the exit code.
|
||||||
func Exec(args []string) int {
|
func Exec(args []string) int {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
@ -17,6 +19,12 @@ func Exec(args []string) int {
|
||||||
return help()
|
return help()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return invalid()
|
_, err := os.Stat(args[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
invalid()
|
||||||
|
}
|
||||||
|
|
||||||
|
return run(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,18 +16,19 @@ func TestExec(t *testing.T) {
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "mac", "--arch", "x86"}), 0)
|
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "mac", "--arch", "x86"}), 0)
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "windows", "--arch", "arm"}), 0)
|
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "windows", "--arch", "arm"}), 0)
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "windows", "--arch", "x86"}), 0)
|
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--dry", "--os", "windows", "--arch", "x86"}), 0)
|
||||||
assert.Equal(t, cli.Exec([]string{"run", "../../examples/hello"}), 0)
|
|
||||||
assert.Equal(t, cli.Exec([]string{"help"}), 0)
|
assert.Equal(t, cli.Exec([]string{"help"}), 0)
|
||||||
|
assert.Equal(t, cli.Exec([]string{"run", "../../examples/hello"}), 0)
|
||||||
|
assert.Equal(t, cli.Exec([]string{"../../examples/script/script.q"}), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecErrors(t *testing.T) {
|
func TestExecErrors(t *testing.T) {
|
||||||
assert.Equal(t, cli.Exec([]string{"build"}), 1)
|
assert.Equal(t, cli.Exec([]string{"build"}), 1)
|
||||||
assert.Equal(t, cli.Exec([]string{"run"}), 1)
|
assert.Equal(t, cli.Exec([]string{"run"}), 1)
|
||||||
|
assert.Equal(t, cli.Exec([]string{"_"}), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecWrongParameters(t *testing.T) {
|
func TestExecWrongParameters(t *testing.T) {
|
||||||
assert.Equal(t, cli.Exec(nil), 2)
|
assert.Equal(t, cli.Exec(nil), 2)
|
||||||
assert.Equal(t, cli.Exec([]string{"_"}), 2)
|
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "--invalid-parameter"}), 2)
|
assert.Equal(t, cli.Exec([]string{"build", "--invalid-parameter"}), 2)
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--invalid-parameter"}), 2)
|
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--invalid-parameter"}), 2)
|
||||||
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--os"}), 2)
|
assert.Equal(t, cli.Exec([]string{"build", "../../examples/hello", "--os"}), 2)
|
||||||
|
|
|
@ -41,6 +41,7 @@ func (s *scanner) scanFile(path string, pkg string) error {
|
||||||
return nil
|
return nil
|
||||||
case token.Invalid:
|
case token.Invalid:
|
||||||
return errors.New(&InvalidCharacter{Character: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
|
return errors.New(&InvalidCharacter{Character: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
|
||||||
|
case token.Script:
|
||||||
default:
|
default:
|
||||||
return errors.New(&InvalidTopLevel{Instruction: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
|
return errors.New(&InvalidTopLevel{Instruction: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ const (
|
||||||
Rune // Rune is a single unicode code point.
|
Rune // Rune is a single unicode code point.
|
||||||
String // String is an uninterpreted series of characters in the source code.
|
String // String is an uninterpreted series of characters in the source code.
|
||||||
Comment // Comment is a comment.
|
Comment // Comment is a comment.
|
||||||
|
Script // Script is a shebang line.
|
||||||
GroupStart // (
|
GroupStart // (
|
||||||
GroupEnd // )
|
GroupEnd // )
|
||||||
BlockStart // {
|
BlockStart // {
|
||||||
|
|
|
@ -37,6 +37,9 @@ func Tokenize(buffer []byte) List {
|
||||||
case '0':
|
case '0':
|
||||||
tokens, i = zero(tokens, buffer, i)
|
tokens, i = zero(tokens, buffer, i)
|
||||||
continue
|
continue
|
||||||
|
case '#':
|
||||||
|
tokens, i = hash(tokens, buffer, i)
|
||||||
|
continue
|
||||||
default:
|
default:
|
||||||
if isIdentifierStart(buffer[i]) {
|
if isIdentifierStart(buffer[i]) {
|
||||||
tokens, i = identifier(tokens, buffer, i)
|
tokens, i = identifier(tokens, buffer, i)
|
||||||
|
|
|
@ -480,6 +480,20 @@ func TestComment(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalid(t *testing.T) {
|
func TestInvalid(t *testing.T) {
|
||||||
|
tokens := token.Tokenize([]byte(`@@`))
|
||||||
|
|
||||||
|
expected := []token.Kind{
|
||||||
|
token.Invalid,
|
||||||
|
token.Invalid,
|
||||||
|
token.EOF,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, kind := range expected {
|
||||||
|
assert.Equal(t, tokens[i].Kind, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidScript(t *testing.T) {
|
||||||
tokens := token.Tokenize([]byte(`##`))
|
tokens := token.Tokenize([]byte(`##`))
|
||||||
|
|
||||||
expected := []token.Kind{
|
expected := []token.Kind{
|
||||||
|
@ -570,6 +584,19 @@ func TestRune(t *testing.T) {
|
||||||
token.EOF,
|
token.EOF,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, kind := range expected {
|
||||||
|
assert.Equal(t, tokens[i].Kind, kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScript(t *testing.T) {
|
||||||
|
tokens := token.Tokenize([]byte("#!/usr/bin/env q"))
|
||||||
|
|
||||||
|
expected := []token.Kind{
|
||||||
|
token.Script,
|
||||||
|
token.EOF,
|
||||||
|
}
|
||||||
|
|
||||||
for i, kind := range expected {
|
for i, kind := range expected {
|
||||||
assert.Equal(t, tokens[i].Kind, kind)
|
assert.Equal(t, tokens[i].Kind, kind)
|
||||||
}
|
}
|
||||||
|
|
19
src/token/hash.go
Normal file
19
src/token/hash.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package token
|
||||||
|
|
||||||
|
// hash handles all tokens starting with '#'.
|
||||||
|
func hash(tokens List, buffer []byte, i Position) (List, Position) {
|
||||||
|
if i+1 < Position(len(buffer)) && buffer[i+1] == '!' {
|
||||||
|
position := i
|
||||||
|
|
||||||
|
for i < Position(len(buffer)) && buffer[i] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens = append(tokens, Token{Kind: Script, Position: position, Length: Length(i - position)})
|
||||||
|
} else {
|
||||||
|
tokens = append(tokens, Token{Kind: Invalid, Position: i, Length: 1})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens, i
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue