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)
|
||||
* Tiny executables ("Hello World" is ~500 bytes)
|
||||
* Fast compilation (5-10x faster than most)
|
||||
* Unix scripting (JIT compilation)
|
||||
* No dependencies (no llvm, no libc)
|
||||
|
||||
## Installation
|
||||
|
@ -25,10 +26,41 @@ go build
|
|||
|
||||
## Usage
|
||||
|
||||
Quick test:
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
```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
|
||||
|
||||
import "os"
|
||||
|
||||
// Exec runs the command included in the first argument and returns the exit code.
|
||||
func Exec(args []string) int {
|
||||
if len(args) == 0 {
|
||||
|
@ -17,6 +19,12 @@ func Exec(args []string) int {
|
|||
return help()
|
||||
|
||||
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", "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{"run", "../../examples/hello"}), 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) {
|
||||
assert.Equal(t, cli.Exec([]string{"build"}), 1)
|
||||
assert.Equal(t, cli.Exec([]string{"run"}), 1)
|
||||
assert.Equal(t, cli.Exec([]string{"_"}), 1)
|
||||
}
|
||||
|
||||
func TestExecWrongParameters(t *testing.T) {
|
||||
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", "../../examples/hello", "--invalid-parameter"}), 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
|
||||
case token.Invalid:
|
||||
return errors.New(&InvalidCharacter{Character: tokens[i].String(file.Bytes)}, file, tokens[i].Position)
|
||||
case token.Script:
|
||||
default:
|
||||
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.
|
||||
String // String is an uninterpreted series of characters in the source code.
|
||||
Comment // Comment is a comment.
|
||||
Script // Script is a shebang line.
|
||||
GroupStart // (
|
||||
GroupEnd // )
|
||||
BlockStart // {
|
||||
|
|
|
@ -37,6 +37,9 @@ func Tokenize(buffer []byte) List {
|
|||
case '0':
|
||||
tokens, i = zero(tokens, buffer, i)
|
||||
continue
|
||||
case '#':
|
||||
tokens, i = hash(tokens, buffer, i)
|
||||
continue
|
||||
default:
|
||||
if isIdentifierStart(buffer[i]) {
|
||||
tokens, i = identifier(tokens, buffer, i)
|
||||
|
|
|
@ -480,6 +480,20 @@ func TestComment(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(`##`))
|
||||
|
||||
expected := []token.Kind{
|
||||
|
@ -570,6 +584,19 @@ func TestRune(t *testing.T) {
|
|||
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 {
|
||||
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