Initial commit
This commit is contained in:
commit
3e2b956786
6 changed files with 559 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
skal
|
||||||
2
Makefile
Normal file
2
Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
run:
|
||||||
|
odin run .
|
||||||
48
main.odin
Normal file
48
main.odin
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
import "parser"
|
||||||
|
import "shell"
|
||||||
|
|
||||||
|
INPUT_MAX :: 4096
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
track: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track, context.temp_allocator)
|
||||||
|
context.temp_allocator = mem.tracking_allocator(&track)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
if len(track.allocation_map) > 0 {
|
||||||
|
fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map))
|
||||||
|
for _, entry in track.allocation_map {
|
||||||
|
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mem.tracking_allocator_destroy(&track)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf: [INPUT_MAX]byte
|
||||||
|
|
||||||
|
for true {
|
||||||
|
prompt := shell.get_prompt()
|
||||||
|
|
||||||
|
fmt.print(prompt)
|
||||||
|
|
||||||
|
buf_len, err := os.read(os.stdin, buf[:])
|
||||||
|
log.assertf(err == nil, "Error reading input")
|
||||||
|
|
||||||
|
input := string(buf[:buf_len])
|
||||||
|
|
||||||
|
cmd_seq, parser_err := parser.parse(input)
|
||||||
|
log.assertf(parser_err == nil, "Could not parse input")
|
||||||
|
|
||||||
|
stop := shell.execute(&cmd_seq)
|
||||||
|
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
if stop == .Stop do break
|
||||||
|
}
|
||||||
|
}
|
||||||
253
parser/parser.odin
Normal file
253
parser/parser.odin
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:slice"
|
||||||
|
import "core:log"
|
||||||
|
|
||||||
|
FORBIDDEN_TOKENS :: []string {
|
||||||
|
"&&",
|
||||||
|
"||",
|
||||||
|
"|",
|
||||||
|
">",
|
||||||
|
"&>",
|
||||||
|
"<",
|
||||||
|
"2>",
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
ParseError :: enum {
|
||||||
|
None = 0,
|
||||||
|
MissingCommand,
|
||||||
|
MissingRedirect,
|
||||||
|
ForbiddenToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
SequenceType :: enum {
|
||||||
|
Head,
|
||||||
|
Or,
|
||||||
|
And
|
||||||
|
}
|
||||||
|
|
||||||
|
Command :: struct {
|
||||||
|
name: cstring,
|
||||||
|
args: [dynamic]cstring,
|
||||||
|
}
|
||||||
|
|
||||||
|
PipeSequence :: struct {
|
||||||
|
rstdin: cstring,
|
||||||
|
rstdout: cstring,
|
||||||
|
rstderr: cstring,
|
||||||
|
|
||||||
|
is_background: bool,
|
||||||
|
|
||||||
|
commands: [dynamic]Command,
|
||||||
|
sequence_type: SequenceType,
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandSequence :: struct {
|
||||||
|
pipe_sequences: [dynamic]PipeSequence,
|
||||||
|
}
|
||||||
|
|
||||||
|
ParserState :: struct {
|
||||||
|
cmd_seq: CommandSequence,
|
||||||
|
cur_pipe_seq: PipeSequence,
|
||||||
|
cur_cmd: Command,
|
||||||
|
|
||||||
|
tokens: []cstring,
|
||||||
|
token_idx: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
new_command :: proc() -> Command {
|
||||||
|
cmd := Command{ name = "" }
|
||||||
|
err: mem.Allocator_Error
|
||||||
|
cmd.args, err = make([dynamic]cstring, 0, 1, context.temp_allocator)
|
||||||
|
log.assertf(err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
new_pipe_sequence :: proc(seq_type: SequenceType) -> PipeSequence {
|
||||||
|
pipe_seq := PipeSequence{ sequence_type = seq_type }
|
||||||
|
err: mem.Allocator_Error
|
||||||
|
pipe_seq.commands, err = make([dynamic]Command, 0, 1, context.temp_allocator)
|
||||||
|
log.assertf(err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
return pipe_seq
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
parse_token :: proc(parser_state: ^ParserState) -> ParseError {
|
||||||
|
token := parser_state.tokens[parser_state.token_idx]
|
||||||
|
|
||||||
|
switch(token) {
|
||||||
|
case "&":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq.is_background = true
|
||||||
|
parser_state^.token_idx += 1
|
||||||
|
case "&&":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
|
||||||
|
_, mem_err := append(&parser_state^.cur_pipe_seq.commands, parser_state^.cur_cmd)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
_, mem_err = append(&parser_state^.cmd_seq.pipe_sequences, parser_state^.cur_pipe_seq)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq = new_pipe_sequence(SequenceType.And)
|
||||||
|
parser_state^.cur_cmd = new_command()
|
||||||
|
parser_state^.token_idx += 1
|
||||||
|
|
||||||
|
case "||":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
|
||||||
|
_, mem_err := append(&parser_state^.cur_pipe_seq.commands, parser_state^.cur_cmd)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
_, mem_err = append(&parser_state^.cmd_seq.pipe_sequences, parser_state^.cur_pipe_seq)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq = new_pipe_sequence(SequenceType.Or)
|
||||||
|
parser_state^.cur_cmd = new_command()
|
||||||
|
parser_state^.token_idx += 1
|
||||||
|
|
||||||
|
case "|":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
|
||||||
|
_, mem_err := append(&parser_state^.cur_pipe_seq.commands, parser_state^.cur_cmd)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
parser_state^.cur_cmd = new_command()
|
||||||
|
parser_state^.token_idx += 1
|
||||||
|
case ">":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
if len(parser_state^.cur_pipe_seq.commands) >= parser_state^.token_idx + 1 do return ParseError.MissingRedirect
|
||||||
|
|
||||||
|
next_token := parser_state.tokens[parser_state.token_idx + 1]
|
||||||
|
if slice.contains(FORBIDDEN_TOKENS, string(next_token)) do return ParseError.ForbiddenToken
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq.rstdout = next_token
|
||||||
|
parser_state^.token_idx += 2
|
||||||
|
case "&>":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
if len(parser_state^.cur_pipe_seq.commands) >= parser_state^.token_idx + 1 do return ParseError.MissingRedirect
|
||||||
|
|
||||||
|
next_token := parser_state.tokens[parser_state.token_idx + 1]
|
||||||
|
if slice.contains(FORBIDDEN_TOKENS, string(next_token)) do return ParseError.ForbiddenToken
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq.rstdout = next_token
|
||||||
|
parser_state^.cur_pipe_seq.rstderr = next_token
|
||||||
|
parser_state^.token_idx += 2
|
||||||
|
case "<":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
if len(parser_state^.cur_pipe_seq.commands) >= parser_state^.token_idx + 1 do return ParseError.MissingRedirect
|
||||||
|
|
||||||
|
next_token := parser_state.tokens[parser_state.token_idx + 1]
|
||||||
|
if slice.contains(FORBIDDEN_TOKENS, string(next_token)) do return ParseError.ForbiddenToken
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq.rstdin = next_token
|
||||||
|
parser_state^.token_idx += 2
|
||||||
|
case "2>":
|
||||||
|
if parser_state^.cur_cmd.name == "" do return ParseError.MissingCommand
|
||||||
|
if len(parser_state^.cur_pipe_seq.commands) >= parser_state^.token_idx + 1 do return ParseError.MissingRedirect
|
||||||
|
|
||||||
|
next_token := parser_state.tokens[parser_state.token_idx + 1]
|
||||||
|
if slice.contains(FORBIDDEN_TOKENS, string(next_token)) do return ParseError.ForbiddenToken
|
||||||
|
|
||||||
|
parser_state^.cur_pipe_seq.rstderr = next_token
|
||||||
|
parser_state^.token_idx += 2
|
||||||
|
case:
|
||||||
|
if parser_state^.cur_cmd.name != "" {
|
||||||
|
_, mem_err := append(&parser_state^.cur_cmd.args, token)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
} else {
|
||||||
|
parser_state^.cur_cmd.name = token
|
||||||
|
}
|
||||||
|
parser_state^.token_idx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
get_cstring_tokens ::proc(input: string) -> []cstring {
|
||||||
|
tokens, mem_err := strings.fields(input, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
c_tokens: []cstring
|
||||||
|
c_tokens, mem_err = make([]cstring, len(tokens), context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
for token, i in tokens {
|
||||||
|
c_tokens[i], mem_err = strings.clone_to_cstring(token, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c_tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
parse :: proc(input: string) -> (cmd_seq: CommandSequence, err: ParseError) {
|
||||||
|
mem_err: mem.Allocator_Error
|
||||||
|
|
||||||
|
cur_cmd_seq := CommandSequence{}
|
||||||
|
cur_cmd_seq.pipe_sequences, mem_err = make([dynamic]PipeSequence, 0, 1, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
c_tokens := get_cstring_tokens(input)
|
||||||
|
|
||||||
|
cur_pipe_seq := new_pipe_sequence(SequenceType.Head)
|
||||||
|
cur_pipe_seq.commands, mem_err = make([dynamic]Command, 0, 1, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
if len(c_tokens) == 0 {
|
||||||
|
return cmd_seq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_cmd := Command{ name = c_tokens[0] }
|
||||||
|
cur_cmd.args, mem_err = make([dynamic]cstring, 0, 1, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
parser_state := ParserState{
|
||||||
|
cmd_seq = cur_cmd_seq,
|
||||||
|
cur_pipe_seq = cur_pipe_seq,
|
||||||
|
cur_cmd = cur_cmd,
|
||||||
|
tokens = c_tokens,
|
||||||
|
token_idx = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for parser_state.token_idx < len(parser_state.tokens) {
|
||||||
|
parse_err := parse_token(&parser_state)
|
||||||
|
switch parse_err {
|
||||||
|
case .MissingRedirect:
|
||||||
|
fmt.printf("Error: Missing redirect target after '%s'\n", parser_state.tokens[parser_state.token_idx])
|
||||||
|
return cmd_seq, parse_err
|
||||||
|
case .MissingCommand:
|
||||||
|
fmt.printf("Error: Missing command before '%s'\n", parser_state.tokens[parser_state.token_idx])
|
||||||
|
return cmd_seq, parse_err
|
||||||
|
case .ForbiddenToken:
|
||||||
|
fmt.printf("Error: Forbidden '%s' after '%s'\n",
|
||||||
|
parser_state.tokens[parser_state.token_idx+1],
|
||||||
|
parser_state.tokens[parser_state.token_idx])
|
||||||
|
return cmd_seq, parse_err
|
||||||
|
case .None:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if parser_state.cur_cmd.name != "" {
|
||||||
|
_, mem_err = append(&parser_state.cur_pipe_seq.commands, parser_state.cur_cmd)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parser_state.cur_pipe_seq.commands) > 0 {
|
||||||
|
_, mem_err = append(&parser_state.cmd_seq.pipe_sequences, parser_state.cur_pipe_seq)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser_state.cmd_seq, nil
|
||||||
|
}
|
||||||
|
|
||||||
197
shell/shell.odin
Normal file
197
shell/shell.odin
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
package shell
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:sys/posix"
|
||||||
|
import "core:os"
|
||||||
|
import "core:log"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
import "../parser"
|
||||||
|
|
||||||
|
ShellState :: enum {
|
||||||
|
Continue,
|
||||||
|
Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
get_prompt :: proc() -> string {
|
||||||
|
dir := os.get_current_directory(context.temp_allocator)
|
||||||
|
home := string(posix.getenv("HOME"))
|
||||||
|
|
||||||
|
if strings.contains(dir, home) {
|
||||||
|
dir, _ = strings.replace(dir, home, "~", 1, context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := posix.getuid()
|
||||||
|
pw := posix.getpwuid(uid)
|
||||||
|
|
||||||
|
user: string
|
||||||
|
if pw == nil {
|
||||||
|
user = "Skal"
|
||||||
|
} else {
|
||||||
|
user = string(pw.pw_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
promt_parts := []string {user, " :: ", dir, " » "}
|
||||||
|
prompt, err := strings.concatenate(promt_parts[:], context.temp_allocator)
|
||||||
|
log.assertf(err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe_command :: proc(pipe_seq: ^parser.PipeSequence, maybe_fd: Maybe(posix.FD), idx: int) {
|
||||||
|
cmd := pipe_seq.commands[idx]
|
||||||
|
|
||||||
|
fd, missing_fd := maybe_fd.?
|
||||||
|
|
||||||
|
if missing_fd {
|
||||||
|
dup_status := posix.dup2(fd, posix.STDOUT_FILENO)
|
||||||
|
log.assertf(dup_status > -1, "Pipe failed")
|
||||||
|
|
||||||
|
close_status := posix.close(fd)
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if idx == 0 {
|
||||||
|
if pipe_seq^.rstdin != "" {
|
||||||
|
stdin_fd := posix.open(pipe_seq^.rstdin, { .RDWR }, { .IRUSR, .IWUSR, .IRGRP, .IROTH })
|
||||||
|
log.assertf(stdin_fd > -1, "Pipe failed")
|
||||||
|
|
||||||
|
dup_status := posix.dup2(stdin_fd, posix.STDIN_FILENO)
|
||||||
|
log.assertf(dup_status > -1, "Pipe failed")
|
||||||
|
|
||||||
|
close_status := posix.close(stdin_fd)
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pipe_seq^.rstdout != "" {
|
||||||
|
stdout_fd := posix.open(pipe_seq^.rstdout, { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH })
|
||||||
|
log.assertf(stdout_fd > -1, "Pipe failed")
|
||||||
|
|
||||||
|
dup_status := posix.dup2(stdout_fd, posix.STDOUT_FILENO)
|
||||||
|
log.assertf(dup_status > -1, "Pipe failed")
|
||||||
|
|
||||||
|
close_status := posix.close(stdout_fd)
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pipe_seq^.rstderr != "" {
|
||||||
|
stderr_fd := posix.open(pipe_seq^.rstderr, { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH })
|
||||||
|
log.assertf(stderr_fd > -1, "Pipe failed")
|
||||||
|
|
||||||
|
dup_status := posix.dup2(stderr_fd, posix.STDERR_FILENO)
|
||||||
|
log.assertf(dup_status > -1, "Pipe failed")
|
||||||
|
|
||||||
|
close_status := posix.close(stderr_fd)
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pipe: [2]posix.FD
|
||||||
|
log.assertf(posix.pipe(&pipe) == .OK, "Pipe failed")
|
||||||
|
|
||||||
|
pid := posix.fork()
|
||||||
|
switch pid {
|
||||||
|
case -1: // Error
|
||||||
|
log.assertf(posix.pipe(&pipe) == .OK, "Fork failed")
|
||||||
|
case 0: // Child
|
||||||
|
close_status := posix.close(pipe[0])
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
|
||||||
|
pipe_command(pipe_seq, pipe[1], idx-1)
|
||||||
|
case: // Parent
|
||||||
|
close_status := posix.close(pipe[1])
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
|
||||||
|
dup_status := posix.dup2(pipe[0], posix.STDIN_FILENO)
|
||||||
|
log.assertf(dup_status > -1, "Pipe failed")
|
||||||
|
|
||||||
|
close_status = posix.close(pipe[0])
|
||||||
|
log.assertf(close_status == .OK, "Pipe failed")
|
||||||
|
|
||||||
|
_ = posix.waitpid(pid, nil, { .UNTRACED, .CONTINUED })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
argv, mem_err := make([]cstring, len(cmd.args)+1, context.temp_allocator)
|
||||||
|
log.assertf(mem_err == nil, "Memory allocation failed")
|
||||||
|
|
||||||
|
argv[0] = cmd.name
|
||||||
|
for i := 0; i < len(cmd.args); i += 1 do argv[i+1] = cmd.args[i]
|
||||||
|
|
||||||
|
_ = posix.execvp(argv[0], raw_data(argv))
|
||||||
|
fmt.printfln("skal: command not found: %s", argv[0])
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
change_dir :: proc(cmd: ^parser.Command) {
|
||||||
|
if len(cmd.args) > 0 {
|
||||||
|
ch_status := posix.chdir(cmd.args[0])
|
||||||
|
if ch_status != .OK do fmt.printf("cd: No such file or directory: %s\n", cmd.args[0])
|
||||||
|
} else {
|
||||||
|
ch_status := posix.chdir(posix.getenv("HOME"))
|
||||||
|
if ch_status != .OK do fmt.printf("cd: No such file or directory: %s\n", posix.getenv("HOME"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_chain :: proc(sequence_type: parser.SequenceType, exec_failed: bool) -> bool {
|
||||||
|
stop_chain: bool
|
||||||
|
|
||||||
|
switch sequence_type {
|
||||||
|
case .Or:
|
||||||
|
stop_chain = !exec_failed
|
||||||
|
case .And:
|
||||||
|
stop_chain = exec_failed
|
||||||
|
case .Head:
|
||||||
|
stop_chain = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return stop_chain
|
||||||
|
}
|
||||||
|
|
||||||
|
execute_cmd_seq :: proc(cmd_seq: ^parser.CommandSequence) {
|
||||||
|
for &pipe_seq in cmd_seq^.pipe_sequences {
|
||||||
|
pid := posix.fork()
|
||||||
|
switch pid {
|
||||||
|
case -1: // Error
|
||||||
|
log.assertf(true, "Fork failed")
|
||||||
|
case 0: // Child
|
||||||
|
pipe_command(&pipe_seq, nil, len(pipe_seq.commands)-1)
|
||||||
|
case: // Parent
|
||||||
|
if pipe_seq.is_background {
|
||||||
|
fmt.printf("Background process: [%d]\n", pid)
|
||||||
|
posix.waitpid(pid, nil, { .NOHANG})
|
||||||
|
} else {
|
||||||
|
status: i32
|
||||||
|
posix.waitpid(pid, &status, { .UNTRACED, .CONTINUED })
|
||||||
|
|
||||||
|
exec_failed := posix.WIFEXITED(status) && posix.WEXITSTATUS(status) != 0
|
||||||
|
|
||||||
|
if stop_chain(pipe_seq.sequence_type, exec_failed) do return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execute :: proc(cmd_seq: ^parser.CommandSequence) -> ShellState {
|
||||||
|
if len(cmd_seq^.pipe_sequences) == 0 do return .Continue
|
||||||
|
|
||||||
|
// if pipe sequence is above 0 it has at least one command
|
||||||
|
first_cmd := cmd_seq^.pipe_sequences[0].commands[0]
|
||||||
|
|
||||||
|
switch first_cmd.name {
|
||||||
|
case "cd":
|
||||||
|
change_dir(&first_cmd)
|
||||||
|
case "exit":
|
||||||
|
return .Stop
|
||||||
|
case:
|
||||||
|
execute_cmd_seq(cmd_seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
58
tests/test.odin
Normal file
58
tests/test.odin
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import "../parser"
|
||||||
|
import "../shell"
|
||||||
|
import "core:testing"
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
testExecution_ok :: proc(t: ^testing.T) {
|
||||||
|
INPUT :: "ls -a"
|
||||||
|
cmd_seq, err := parser.parse(INPUT)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", INPUT)
|
||||||
|
|
||||||
|
shell.execute(&cmd_seq)
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
testPipe_ok :: proc(t: ^testing.T) {
|
||||||
|
SINGLE_PIPE :: "ls -a | wc"
|
||||||
|
cmd_seq, err := parser.parse(SINGLE_PIPE)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", SINGLE_PIPE)
|
||||||
|
shell.execute(&cmd_seq)
|
||||||
|
|
||||||
|
DOUBLE_PIPE :: "ls -a | ls -b | wc"
|
||||||
|
cmd_seq, err = parser.parse(DOUBLE_PIPE)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", DOUBLE_PIPE)
|
||||||
|
|
||||||
|
shell.execute(&cmd_seq)
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
testOkChain_ok :: proc(t: ^testing.T) {
|
||||||
|
SINGLE_CHAIN :: "ls -a && ls -b"
|
||||||
|
cmd_seq, err := parser.parse(SINGLE_CHAIN)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", SINGLE_CHAIN)
|
||||||
|
|
||||||
|
DOUBLE_CHAIN :: "ls -a && ls -b && ls -c"
|
||||||
|
cmd_seq, err = parser.parse(DOUBLE_CHAIN)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", DOUBLE_CHAIN)
|
||||||
|
|
||||||
|
shell.execute(&cmd_seq)
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
testBadChain_ok :: proc(t: ^testing.T) {
|
||||||
|
SINGLE_CHAIN :: "xyz || ls -a"
|
||||||
|
cmd_seq, err := parser.parse(SINGLE_CHAIN)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", SINGLE_CHAIN)
|
||||||
|
|
||||||
|
DOUBLE_CHAIN :: "xyz || xyz || ls -a"
|
||||||
|
cmd_seq, err = parser.parse(DOUBLE_CHAIN)
|
||||||
|
testing.expect(t, err == nil, "Parsing of %s failed", DOUBLE_CHAIN)
|
||||||
|
|
||||||
|
shell.execute(&cmd_seq)
|
||||||
|
free_all(context.temp_allocator)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue