Compare commits
87 Commits
arghing
...
d4b5c737e5
Author | SHA1 | Date | |
---|---|---|---|
d4b5c737e5
|
|||
5381d5f0ce
|
|||
ea9cb1f846
|
|||
58e734c60f
|
|||
df5d4f5a1f
|
|||
32e70e96ce
|
|||
8af4f619f4
|
|||
52a5ea57e1
|
|||
1a894440f9
|
|||
353b77271d
|
|||
8f9842ddfb
|
|||
94cd12ac82
|
|||
726566bb23
|
|||
7e07054a70
|
|||
af95125894
|
|||
7da7228911
|
|||
88133117ab
|
|||
c8ac7ac727
|
|||
a135c338ea
|
|||
0fd7b5873d
|
|||
e28293e5bd
|
|||
58002830a8
|
|||
8532dd4657
|
|||
f6ac67a6a6
|
|||
8424a6836a
|
|||
8e2f5c1ff0
|
|||
9183869a50
|
|||
1ed7fb7d33
|
|||
a498ff949c
|
|||
ad65630b3f
|
|||
9109e3a3dc
|
|||
9d36627e87
|
|||
db38eeada9
|
|||
cb68a35b1b
|
|||
01a189c5ee
|
|||
3580198513
|
|||
efb8f453ae
|
|||
5df7489f27
|
|||
efc1c8f029
|
|||
43a2e51712
|
|||
88e4e319d3
|
|||
c274f59f58
|
|||
c8a4928ee8
|
|||
6d523ad7c1
|
|||
d9393152c5
|
|||
2f3ce704b2
|
|||
54a7a2340c
|
|||
8cc2e18965
|
|||
ffb16efcdc
|
|||
10ec594031
|
|||
cee338520e
|
|||
e92d117a41
|
|||
dc9c3fd54d
|
|||
a8d82b180a
|
|||
337a795ad0
|
|||
3f369e1e70
|
|||
a3819c9b26
|
|||
345464d0d4
|
|||
5b55118373
|
|||
accf0d0c2b
|
|||
b672131cfc
|
|||
d03225edce
|
|||
8434bf7025
|
|||
087355502d
|
|||
0e6448df23
|
|||
4da817996d
|
|||
7e0aa3b86b
|
|||
aa83db6f7a
|
|||
9d4f5b661b
|
|||
b94fb3b31e
|
|||
dacbddca6e
|
|||
6e50ec65f8
|
|||
ca812add16
|
|||
460741740b
|
|||
7204f08177
|
|||
505e04613f
|
|||
7f4f627769
|
|||
dc8045a20e
|
|||
41f53c34c0
|
|||
6c0083e094
|
|||
7f2f579241
|
|||
7d2ac20ce1
|
|||
8f38a3b271
|
|||
96f5f85a27
|
|||
6cf80e09b1
|
|||
30ed115ed0
|
|||
8376608a1e |
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
max_line_length = 90
|
||||||
|
|
||||||
|
[{*.c,*.go}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@
|
|||||||
**/target/
|
**/target/
|
||||||
/hello_world/main
|
/hello_world/main
|
||||||
/aoc*/**/input
|
/aoc*/**/input
|
||||||
|
/arduino/build-*/
|
||||||
|
2
arduino/.envrc
Normal file
2
arduino/.envrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export ARDMK_DIR=/usr/share/arduino
|
||||||
|
export ARDMK_VENDOR=archlinux-arduino
|
3
arduino/Makefile
Normal file
3
arduino/Makefile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
BOARD_TAG = uno
|
||||||
|
|
||||||
|
include $(ARDMK_DIR)/Arduino.mk
|
31
arduino/sos.ino
Normal file
31
arduino/sos.ino
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#define DIT_DURATION_MS 88
|
||||||
|
#define LETTER_PAUSE_MS 1000
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
pinMode(13, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dit() {
|
||||||
|
digitalWrite(13, HIGH);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
digitalWrite(13, LOW);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dah() {
|
||||||
|
digitalWrite(13, HIGH);
|
||||||
|
delay(DIT_DURATION_MS * 3);
|
||||||
|
digitalWrite(13, LOW);
|
||||||
|
delay(DIT_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
dit(); dit(); dit();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
|
||||||
|
dah();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
|
||||||
|
dit(); dit(); dit();
|
||||||
|
delay(LETTER_PAUSE_MS);
|
||||||
|
}
|
@@ -1,4 +0,0 @@
|
|||||||
# argh command line parser
|
|
||||||
|
|
||||||
> NOTE: much of this is lifted from
|
|
||||||
> https://blog.gopheracademy.com/advent-2014/parsers-lexers/
|
|
34
argh/argh.go
34
argh/argh.go
@@ -1,34 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
tracingEnabled = os.Getenv("ARGH_TRACING") == "enabled"
|
|
||||||
traceLogger *log.Logger
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if !tracingEnabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
traceLogger = log.New(os.Stderr, "ARGH TRACING: ", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tracef(format string, v ...any) {
|
|
||||||
if !tracingEnabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, file, line, ok := runtime.Caller(1); ok {
|
|
||||||
format = fmt.Sprintf("%v:%v ", filepath.Base(file), line) + format
|
|
||||||
}
|
|
||||||
|
|
||||||
traceLogger.Printf(format, v...)
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.meatballhat.com/x/box-o-sand/argh"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
asJSON := os.Getenv("ARGH_OUTPUT_JSON") == "enabled"
|
|
||||||
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
pt, err := argh.ParseArgs(os.Args, argh.NewParserConfig(
|
|
||||||
&argh.CommandConfig{
|
|
||||||
NValue: argh.OneOrMoreValue,
|
|
||||||
ValueNames: []string{"topping"},
|
|
||||||
Flags: &argh.Flags{
|
|
||||||
Automatic: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ast := argh.NewQuerier(pt.Nodes).AST()
|
|
||||||
|
|
||||||
if asJSON {
|
|
||||||
b, err := json.MarshalIndent(ast, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(b))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
spew.Dump(ast)
|
|
||||||
}
|
|
12
argh/go.mod
12
argh/go.mod
@@ -1,12 +0,0 @@
|
|||||||
module git.meatballhat.com/x/box-o-sand/argh
|
|
||||||
|
|
||||||
go 1.18
|
|
||||||
|
|
||||||
require github.com/pkg/errors v0.9.1
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/stretchr/testify v1.7.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
|
||||||
)
|
|
12
argh/go.sum
12
argh/go.sum
@@ -1,12 +0,0 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
46
argh/node.go
46
argh/node.go
@@ -1,46 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
type Node interface{}
|
|
||||||
|
|
||||||
type TypedNode struct {
|
|
||||||
Type string
|
|
||||||
Node Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type PassthroughArgs struct {
|
|
||||||
Nodes []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompoundShortFlag struct {
|
|
||||||
Nodes []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ident struct {
|
|
||||||
Literal string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BadArg struct {
|
|
||||||
Literal string
|
|
||||||
From Pos
|
|
||||||
To Pos
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Name string
|
|
||||||
Values map[string]string
|
|
||||||
Nodes []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type Flag struct {
|
|
||||||
Name string
|
|
||||||
Values map[string]string
|
|
||||||
Nodes []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type StdinFlag struct{}
|
|
||||||
|
|
||||||
type StopFlag struct{}
|
|
||||||
|
|
||||||
type ArgDelimiter struct{}
|
|
||||||
|
|
||||||
type Assign struct{}
|
|
@@ -1,26 +0,0 @@
|
|||||||
// Code generated by "stringer -type NValue"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package argh
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
|
||||||
// Re-run the stringer command to generate them again.
|
|
||||||
var x [1]struct{}
|
|
||||||
_ = x[OneOrMoreValue - -2]
|
|
||||||
_ = x[ZeroOrMoreValue - -1]
|
|
||||||
_ = x[ZeroValue-0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _NValue_name = "OneOrMoreValueZeroOrMoreValueZeroValue"
|
|
||||||
|
|
||||||
var _NValue_index = [...]uint8{0, 14, 29, 38}
|
|
||||||
|
|
||||||
func (i NValue) String() string {
|
|
||||||
i -= -2
|
|
||||||
if i < 0 || i >= NValue(len(_NValue_index)-1) {
|
|
||||||
return "NValue(" + strconv.FormatInt(int64(i+-2), 10) + ")"
|
|
||||||
}
|
|
||||||
return _NValue_name[_NValue_index[i]:_NValue_index[i+1]]
|
|
||||||
}
|
|
346
argh/parser.go
346
argh/parser.go
@@ -1,346 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
s *Scanner
|
|
||||||
|
|
||||||
cfg *ParserConfig
|
|
||||||
|
|
||||||
errors ParserErrorList
|
|
||||||
|
|
||||||
tok Token
|
|
||||||
lit string
|
|
||||||
pos Pos
|
|
||||||
|
|
||||||
buffered bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParseTree struct {
|
|
||||||
Nodes []Node `json:"nodes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseArgs(args []string, pCfg *ParserConfig) (*ParseTree, error) {
|
|
||||||
p := &parser{}
|
|
||||||
p.init(
|
|
||||||
strings.NewReader(strings.Join(args, string(nul))),
|
|
||||||
pCfg,
|
|
||||||
)
|
|
||||||
|
|
||||||
tracef("ParseArgs(...) parser=%+#v", p)
|
|
||||||
|
|
||||||
return p.parseArgs()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) addError(msg string) {
|
|
||||||
p.errors.Add(Position{Column: int(p.pos)}, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) init(r io.Reader, pCfg *ParserConfig) {
|
|
||||||
p.errors = ParserErrorList{}
|
|
||||||
|
|
||||||
if pCfg == nil {
|
|
||||||
pCfg = POSIXyParserConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cfg = pCfg
|
|
||||||
|
|
||||||
p.s = NewScanner(r, pCfg.ScannerConfig)
|
|
||||||
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseArgs() (*ParseTree, error) {
|
|
||||||
if p.errors.Len() != 0 {
|
|
||||||
tracef("parseArgs() bailing due to initial error")
|
|
||||||
return nil, p.errors.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
tracef("parseArgs() parsing %q as program command; cfg=%+#v", p.lit, p.cfg.Prog)
|
|
||||||
prog := p.parseCommand(&p.cfg.Prog)
|
|
||||||
|
|
||||||
tracef("parseArgs() top level node is %T", prog)
|
|
||||||
|
|
||||||
nodes := []Node{prog}
|
|
||||||
if v := p.parsePassthrough(); v != nil {
|
|
||||||
tracef("parseArgs() appending passthrough argument %v", v)
|
|
||||||
nodes = append(nodes, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
tracef("parseArgs() returning ParseTree")
|
|
||||||
|
|
||||||
return &ParseTree{Nodes: nodes}, p.errors.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) next() {
|
|
||||||
tracef("next() before scan: %v %q %v", p.tok, p.lit, p.pos)
|
|
||||||
|
|
||||||
p.tok, p.lit, p.pos = p.s.Scan()
|
|
||||||
|
|
||||||
tracef("next() after scan: %v %q %v", p.tok, p.lit, p.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseCommand(cCfg *CommandConfig) Node {
|
|
||||||
tracef("parseCommand(%+#v)", cCfg)
|
|
||||||
|
|
||||||
node := &Command{
|
|
||||||
Name: p.lit,
|
|
||||||
}
|
|
||||||
values := map[string]string{}
|
|
||||||
nodes := []Node{}
|
|
||||||
|
|
||||||
identIndex := 0
|
|
||||||
|
|
||||||
for i := 0; p.tok != EOL; i++ {
|
|
||||||
if !p.buffered {
|
|
||||||
tracef("parseCommand(...) buffered=false; scanning next")
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.buffered = false
|
|
||||||
|
|
||||||
tracef("parseCommand(...) for=%d values=%+#v", i, values)
|
|
||||||
tracef("parseCommand(...) for=%d nodes=%+#v", i, nodes)
|
|
||||||
tracef("parseCommand(...) for=%d tok=%s lit=%q pos=%v", i, p.tok, p.lit, p.pos)
|
|
||||||
|
|
||||||
tracef("parseCommand(...) cCfg=%+#v", cCfg)
|
|
||||||
|
|
||||||
if subCfg, ok := cCfg.GetCommandConfig(p.lit); ok {
|
|
||||||
subCommand := p.lit
|
|
||||||
|
|
||||||
nodes = append(nodes, p.parseCommand(&subCfg))
|
|
||||||
|
|
||||||
tracef("parseCommand(...) breaking after sub-command=%v", subCommand)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p.tok {
|
|
||||||
case ARG_DELIMITER:
|
|
||||||
tracef("parseCommand(...) handling %s", p.tok)
|
|
||||||
|
|
||||||
nodes = append(nodes, &ArgDelimiter{})
|
|
||||||
|
|
||||||
continue
|
|
||||||
case IDENT, STDIN_FLAG:
|
|
||||||
tracef("parseCommand(...) handling %s", p.tok)
|
|
||||||
|
|
||||||
if cCfg.NValue.Contains(identIndex) {
|
|
||||||
name := fmt.Sprintf("%d", identIndex)
|
|
||||||
|
|
||||||
tracef("parseCommand(...) checking for name of identIndex=%d", identIndex)
|
|
||||||
|
|
||||||
if len(cCfg.ValueNames) > identIndex {
|
|
||||||
name = cCfg.ValueNames[identIndex]
|
|
||||||
tracef("parseCommand(...) setting name=%s from config value names", name)
|
|
||||||
} else if len(cCfg.ValueNames) == 1 && (cCfg.NValue == OneOrMoreValue || cCfg.NValue == ZeroOrMoreValue) {
|
|
||||||
name = fmt.Sprintf("%s.%d", cCfg.ValueNames[0], identIndex)
|
|
||||||
tracef("parseCommand(...) setting name=%s from repeating value name", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
values[name] = p.lit
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.tok == STDIN_FLAG {
|
|
||||||
nodes = append(nodes, &StdinFlag{})
|
|
||||||
} else {
|
|
||||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
|
||||||
}
|
|
||||||
|
|
||||||
identIndex++
|
|
||||||
case LONG_FLAG, SHORT_FLAG, COMPOUND_SHORT_FLAG:
|
|
||||||
tok := p.tok
|
|
||||||
|
|
||||||
flagNode := p.parseFlag(cCfg.Flags)
|
|
||||||
|
|
||||||
tracef("parseCommand(...) appending %s node=%+#v", tok, flagNode)
|
|
||||||
|
|
||||||
nodes = append(nodes, flagNode)
|
|
||||||
case ASSIGN:
|
|
||||||
tracef("parseCommand(...) error on bare %s", p.tok)
|
|
||||||
|
|
||||||
p.addError("invalid bare assignment")
|
|
||||||
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
tracef("parseCommand(...) breaking on %s", p.tok)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodes) > 0 {
|
|
||||||
node.Nodes = nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
node.Values = values
|
|
||||||
}
|
|
||||||
|
|
||||||
tracef("parseCommand(...) returning node=%+#v", node)
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseIdent() Node {
|
|
||||||
node := &Ident{Literal: p.lit}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseFlag(flags *Flags) Node {
|
|
||||||
switch p.tok {
|
|
||||||
case SHORT_FLAG:
|
|
||||||
tracef("parseFlag(...) parsing short flag with config=%+#v", flags)
|
|
||||||
return p.parseShortFlag(flags)
|
|
||||||
case LONG_FLAG:
|
|
||||||
tracef("parseFlag(...) parsing long flag with config=%+#v", flags)
|
|
||||||
return p.parseLongFlag(flags)
|
|
||||||
case COMPOUND_SHORT_FLAG:
|
|
||||||
tracef("parseFlag(...) parsing compound short flag with config=%+#v", flags)
|
|
||||||
return p.parseCompoundShortFlag(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Sprintf("token %v cannot be parsed as flag", p.tok))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseShortFlag(flags *Flags) Node {
|
|
||||||
node := &Flag{Name: string(p.lit[1])}
|
|
||||||
|
|
||||||
flCfg, ok := flags.Get(node.Name)
|
|
||||||
if !ok {
|
|
||||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.parseConfiguredFlag(node, flCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseLongFlag(flags *Flags) Node {
|
|
||||||
node := &Flag{Name: string(p.lit[2:])}
|
|
||||||
|
|
||||||
flCfg, ok := flags.Get(node.Name)
|
|
||||||
if !ok {
|
|
||||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.parseConfiguredFlag(node, flCfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseCompoundShortFlag(flags *Flags) Node {
|
|
||||||
flagNodes := []Node{}
|
|
||||||
|
|
||||||
withoutFlagPrefix := p.lit[1:]
|
|
||||||
|
|
||||||
for i, r := range withoutFlagPrefix {
|
|
||||||
node := &Flag{Name: string(r)}
|
|
||||||
|
|
||||||
if i == len(withoutFlagPrefix)-1 {
|
|
||||||
flCfg, ok := flags.Get(node.Name)
|
|
||||||
if !ok {
|
|
||||||
p.addError(fmt.Sprintf("unknown flag %q", node.Name))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
flagNodes = append(flagNodes, p.parseConfiguredFlag(node, flCfg))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
flagNodes = append(flagNodes, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CompoundShortFlag{Nodes: flagNodes}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parseConfiguredFlag(node *Flag, flCfg FlagConfig) Node {
|
|
||||||
values := map[string]string{}
|
|
||||||
nodes := []Node{}
|
|
||||||
|
|
||||||
identIndex := 0
|
|
||||||
|
|
||||||
for i := 0; p.tok != EOL; i++ {
|
|
||||||
if !flCfg.NValue.Contains(identIndex) {
|
|
||||||
tracef("parseConfiguredFlag(...) identIndex=%d exceeds expected=%v; breaking", identIndex, flCfg.NValue)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
p.next()
|
|
||||||
|
|
||||||
switch p.tok {
|
|
||||||
case ARG_DELIMITER:
|
|
||||||
nodes = append(nodes, &ArgDelimiter{})
|
|
||||||
|
|
||||||
continue
|
|
||||||
case ASSIGN:
|
|
||||||
nodes = append(nodes, &Assign{})
|
|
||||||
|
|
||||||
continue
|
|
||||||
case IDENT, STDIN_FLAG:
|
|
||||||
name := fmt.Sprintf("%d", identIndex)
|
|
||||||
|
|
||||||
tracef("parseConfiguredFlag(...) checking for name of identIndex=%d", identIndex)
|
|
||||||
|
|
||||||
if len(flCfg.ValueNames) > identIndex {
|
|
||||||
name = flCfg.ValueNames[identIndex]
|
|
||||||
tracef("parseConfiguredFlag(...) setting name=%s from config value names", name)
|
|
||||||
} else if len(flCfg.ValueNames) == 1 && (flCfg.NValue == OneOrMoreValue || flCfg.NValue == ZeroOrMoreValue) {
|
|
||||||
name = fmt.Sprintf("%s.%d", flCfg.ValueNames[0], identIndex)
|
|
||||||
tracef("parseConfiguredFlag(...) setting name=%s from repeating value name", name)
|
|
||||||
} else {
|
|
||||||
tracef("parseConfiguredFlag(...) setting name=%s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
values[name] = p.lit
|
|
||||||
|
|
||||||
if p.tok == STDIN_FLAG {
|
|
||||||
nodes = append(nodes, &StdinFlag{})
|
|
||||||
} else {
|
|
||||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
|
||||||
}
|
|
||||||
|
|
||||||
identIndex++
|
|
||||||
default:
|
|
||||||
tracef("parseConfiguredFlag(...) breaking on %s %q %v; setting buffered=true", p.tok, p.lit, p.pos)
|
|
||||||
p.buffered = true
|
|
||||||
|
|
||||||
if len(nodes) > 0 {
|
|
||||||
node.Nodes = nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
node.Values = values
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodes) > 0 {
|
|
||||||
node.Nodes = nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(values) > 0 {
|
|
||||||
node.Values = values
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) parsePassthrough() Node {
|
|
||||||
nodes := []Node{}
|
|
||||||
|
|
||||||
for ; p.tok != EOL; p.next() {
|
|
||||||
nodes = append(nodes, &Ident{Literal: p.lit})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(nodes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PassthroughArgs{Nodes: nodes}
|
|
||||||
}
|
|
@@ -1,146 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
const (
|
|
||||||
OneOrMoreValue NValue = -2
|
|
||||||
ZeroOrMoreValue NValue = -1
|
|
||||||
ZeroValue NValue = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
POSIXyParserConfig = NewParserConfig(
|
|
||||||
nil,
|
|
||||||
POSIXyScannerConfig,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
type NValue int
|
|
||||||
|
|
||||||
func (nv NValue) Contains(i int) bool {
|
|
||||||
tracef("NValue.Contains(%v)", i)
|
|
||||||
|
|
||||||
if i < int(ZeroValue) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if nv == OneOrMoreValue || nv == ZeroOrMoreValue {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(nv) > i
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParserConfig struct {
|
|
||||||
Prog CommandConfig
|
|
||||||
|
|
||||||
ScannerConfig *ScannerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParserConfig(prog *CommandConfig, sCfg *ScannerConfig) *ParserConfig {
|
|
||||||
if sCfg == nil {
|
|
||||||
sCfg = POSIXyScannerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
if prog == nil {
|
|
||||||
prog = &CommandConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
prog.init()
|
|
||||||
|
|
||||||
pCfg := &ParserConfig{
|
|
||||||
Prog: *prog,
|
|
||||||
ScannerConfig: sCfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
return pCfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandConfig struct {
|
|
||||||
NValue NValue
|
|
||||||
ValueNames []string
|
|
||||||
Flags *Flags
|
|
||||||
Commands *Commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cCfg *CommandConfig) init() {
|
|
||||||
if cCfg.ValueNames == nil {
|
|
||||||
cCfg.ValueNames = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cCfg.Flags == nil {
|
|
||||||
cCfg.Flags = &Flags{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cCfg.Commands == nil {
|
|
||||||
cCfg.Commands = &Commands{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cCfg *CommandConfig) GetCommandConfig(name string) (CommandConfig, bool) {
|
|
||||||
tracef("CommandConfig.GetCommandConfig(%q)", name)
|
|
||||||
|
|
||||||
if cCfg.Commands == nil {
|
|
||||||
cCfg.Commands = &Commands{Map: map[string]CommandConfig{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cCfg.Commands.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cCfg *CommandConfig) GetFlagConfig(name string) (FlagConfig, bool) {
|
|
||||||
tracef("CommandConfig.GetFlagConfig(%q)", name)
|
|
||||||
|
|
||||||
if cCfg.Flags == nil {
|
|
||||||
cCfg.Flags = &Flags{Map: map[string]FlagConfig{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cCfg.Flags.Get(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FlagConfig struct {
|
|
||||||
NValue NValue
|
|
||||||
Persist bool
|
|
||||||
ValueNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Flags struct {
|
|
||||||
Parent *Flags
|
|
||||||
Map map[string]FlagConfig
|
|
||||||
|
|
||||||
Automatic bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fl *Flags) Get(name string) (FlagConfig, bool) {
|
|
||||||
tracef("Flags.Get(%q)", name)
|
|
||||||
|
|
||||||
if fl.Map == nil {
|
|
||||||
fl.Map = map[string]FlagConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
flCfg, ok := fl.Map[name]
|
|
||||||
if !ok {
|
|
||||||
if fl.Automatic {
|
|
||||||
return FlagConfig{}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
if fl.Parent != nil {
|
|
||||||
flCfg, ok = fl.Parent.Get(name)
|
|
||||||
return flCfg, ok && flCfg.Persist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flCfg, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type Commands struct {
|
|
||||||
Map map[string]CommandConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd *Commands) Get(name string) (CommandConfig, bool) {
|
|
||||||
tracef("Commands.Get(%q)", name)
|
|
||||||
|
|
||||||
if cmd.Map == nil {
|
|
||||||
cmd.Map = map[string]CommandConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdCfg, ok := cmd.Map[name]
|
|
||||||
return cmdCfg, ok
|
|
||||||
}
|
|
@@ -1,88 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParserError is largely borrowed from go/scanner.Error
|
|
||||||
type ParserError struct {
|
|
||||||
Pos Position
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ParserError) Error() string {
|
|
||||||
if e.Pos.IsValid() {
|
|
||||||
return e.Pos.String() + ":" + e.Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParserErrorList is largely borrowed from go/scanner.ErrorList
|
|
||||||
type ParserErrorList []*ParserError
|
|
||||||
|
|
||||||
func (el *ParserErrorList) Add(pos Position, msg string) {
|
|
||||||
*el = append(*el, &ParserError{Pos: pos, Msg: msg})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el *ParserErrorList) Reset() { *el = (*el)[0:0] }
|
|
||||||
|
|
||||||
func (el ParserErrorList) Len() int { return len(el) }
|
|
||||||
|
|
||||||
func (el ParserErrorList) Swap(i, j int) { el[i], el[j] = el[j], el[i] }
|
|
||||||
|
|
||||||
func (el ParserErrorList) Less(i, j int) bool {
|
|
||||||
e := &el[i].Pos
|
|
||||||
f := &el[j].Pos
|
|
||||||
|
|
||||||
if e.Column != f.Column {
|
|
||||||
return e.Column < f.Column
|
|
||||||
}
|
|
||||||
|
|
||||||
return el[i].Msg < el[j].Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el ParserErrorList) Sort() {
|
|
||||||
sort.Sort(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el ParserErrorList) Error() string {
|
|
||||||
switch len(el) {
|
|
||||||
case 0:
|
|
||||||
return "no errors"
|
|
||||||
case 1:
|
|
||||||
return el[0].Error()
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (and %d more errors)", el[0], len(el)-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el ParserErrorList) Err() error {
|
|
||||||
if len(el) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
func (el ParserErrorList) Is(other error) bool {
|
|
||||||
if _, ok := other.(ParserErrorList); ok {
|
|
||||||
return el.Error() == other.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := other.(*ParserErrorList); ok {
|
|
||||||
return el.Error() == (*v).Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrintParserError(w io.Writer, err error) {
|
|
||||||
if list, ok := err.(ParserErrorList); ok {
|
|
||||||
for _, e := range list {
|
|
||||||
fmt.Fprintf(w, "%s\n", e)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
fmt.Fprintf(w, "%s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
1028
argh/parser_test.go
1028
argh/parser_test.go
File diff suppressed because it is too large
Load Diff
@@ -1,94 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
type Querier interface {
|
|
||||||
Program() (*Command, bool)
|
|
||||||
AST() []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewQuerier(nodes []Node) Querier {
|
|
||||||
return &defaultQuerier{nodes: nodes}
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultQuerier struct {
|
|
||||||
nodes []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dq *defaultQuerier) Program() (*Command, bool) {
|
|
||||||
if len(dq.nodes) == 0 {
|
|
||||||
tracef("Program nodes are empty")
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
tracef("Program node[0] is %T", dq.nodes[0])
|
|
||||||
|
|
||||||
v, ok := dq.nodes[0].(*Command)
|
|
||||||
if ok && v.Name == "" {
|
|
||||||
return v, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dq *defaultQuerier) AST() []Node {
|
|
||||||
ret := []Node{}
|
|
||||||
|
|
||||||
for i, node := range dq.nodes {
|
|
||||||
tracef("AST i=%d node type=%T", i, node)
|
|
||||||
|
|
||||||
if _, ok := node.(*ArgDelimiter); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := node.(*StopFlag); ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := node.(*CompoundShortFlag); ok {
|
|
||||||
if v.Nodes != nil {
|
|
||||||
ret = append(ret, NewQuerier(v.Nodes).AST()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := node.(*Command); ok {
|
|
||||||
astNodes := NewQuerier(v.Nodes).AST()
|
|
||||||
|
|
||||||
if len(astNodes) == 0 {
|
|
||||||
astNodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(
|
|
||||||
ret,
|
|
||||||
&Command{
|
|
||||||
Name: v.Name,
|
|
||||||
Values: v.Values,
|
|
||||||
Nodes: astNodes,
|
|
||||||
})
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := node.(*Flag); ok {
|
|
||||||
astNodes := NewQuerier(v.Nodes).AST()
|
|
||||||
|
|
||||||
if len(astNodes) == 0 {
|
|
||||||
astNodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(
|
|
||||||
ret,
|
|
||||||
&Flag{
|
|
||||||
Name: v.Name,
|
|
||||||
Values: v.Values,
|
|
||||||
Nodes: astNodes,
|
|
||||||
})
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
package argh_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.meatballhat.com/x/box-o-sand/argh"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestQuerier_Program(t *testing.T) {
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
cfg *argh.ParserConfig
|
|
||||||
exp string
|
|
||||||
expOK bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "typical",
|
|
||||||
args: []string{"pizzas", "ahoy", "--treatsa", "fun"},
|
|
||||||
cfg: &argh.ParserConfig{
|
|
||||||
Prog: argh.CommandConfig{
|
|
||||||
Commands: &argh.Commands{
|
|
||||||
Map: map[string]argh.CommandConfig{
|
|
||||||
"ahoy": argh.CommandConfig{
|
|
||||||
Flags: &argh.Flags{
|
|
||||||
Map: map[string]argh.FlagConfig{
|
|
||||||
"treatsa": argh.FlagConfig{NValue: 1},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
exp: "pizzas",
|
|
||||||
expOK: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "minimal",
|
|
||||||
args: []string{"pizzas"},
|
|
||||||
exp: "pizzas",
|
|
||||||
expOK: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid",
|
|
||||||
args: []string{},
|
|
||||||
expOK: false,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(ct *testing.T) {
|
|
||||||
pt, err := argh.ParseArgs(tc.args, tc.cfg)
|
|
||||||
require.Nil(ct, err)
|
|
||||||
|
|
||||||
prog, ok := argh.NewQuerier(pt.Nodes).Program()
|
|
||||||
require.Equal(ct, tc.expOK, ok)
|
|
||||||
require.Equal(ct, tc.exp, prog.Name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
159
argh/scanner.go
159
argh/scanner.go
@@ -1,159 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Scanner struct {
|
|
||||||
r *bufio.Reader
|
|
||||||
i int
|
|
||||||
cfg *ScannerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewScanner(r io.Reader, cfg *ScannerConfig) *Scanner {
|
|
||||||
if cfg == nil {
|
|
||||||
cfg = POSIXyScannerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Scanner{
|
|
||||||
r: bufio.NewReader(r),
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Scanner) Scan() (Token, string, Pos) {
|
|
||||||
ch, pos := s.read()
|
|
||||||
|
|
||||||
if s.cfg.IsBlankspace(ch) {
|
|
||||||
_ = s.unread()
|
|
||||||
return s.scanBlankspace()
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cfg.IsAssignmentOperator(ch) {
|
|
||||||
return ASSIGN, string(ch), pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cfg.IsMultiValueDelim(ch) {
|
|
||||||
return MULTI_VALUE_DELIMITER, string(ch), pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch == eol {
|
|
||||||
return EOL, "", pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch == nul {
|
|
||||||
return ARG_DELIMITER, string(ch), pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if unicode.IsGraphic(ch) {
|
|
||||||
_ = s.unread()
|
|
||||||
return s.scanArg()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ILLEGAL, string(ch), pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Scanner) read() (rune, Pos) {
|
|
||||||
ch, _, err := s.r.ReadRune()
|
|
||||||
s.i++
|
|
||||||
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return eol, Pos(s.i)
|
|
||||||
} else if err != nil {
|
|
||||||
log.Printf("unknown scanner error=%+v", err)
|
|
||||||
return eol, Pos(s.i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ch, Pos(s.i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Scanner) unread() Pos {
|
|
||||||
_ = s.r.UnreadRune()
|
|
||||||
s.i--
|
|
||||||
return Pos(s.i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Scanner) scanBlankspace() (Token, string, Pos) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
ch, pos := s.read()
|
|
||||||
buf.WriteRune(ch)
|
|
||||||
|
|
||||||
for {
|
|
||||||
ch, pos = s.read()
|
|
||||||
|
|
||||||
if ch == eol {
|
|
||||||
break
|
|
||||||
} else if !s.cfg.IsBlankspace(ch) {
|
|
||||||
pos = s.unread()
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
_, _ = buf.WriteRune(ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return BS, buf.String(), pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Scanner) scanArg() (Token, string, Pos) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
ch, pos := s.read()
|
|
||||||
buf.WriteRune(ch)
|
|
||||||
|
|
||||||
for {
|
|
||||||
ch, pos = s.read()
|
|
||||||
|
|
||||||
if ch == eol || ch == nul || s.cfg.IsAssignmentOperator(ch) || s.cfg.IsMultiValueDelim(ch) {
|
|
||||||
pos = s.unread()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = buf.WriteRune(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
str := buf.String()
|
|
||||||
|
|
||||||
if len(str) == 0 {
|
|
||||||
return EMPTY, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
ch0 := rune(str[0])
|
|
||||||
|
|
||||||
if len(str) == 1 {
|
|
||||||
if s.cfg.IsFlagPrefix(ch0) {
|
|
||||||
return STDIN_FLAG, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cfg.IsAssignmentOperator(ch0) {
|
|
||||||
return ASSIGN, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDENT, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
ch1 := rune(str[1])
|
|
||||||
|
|
||||||
if len(str) == 2 {
|
|
||||||
if s.cfg.IsFlagPrefix(ch0) && s.cfg.IsFlagPrefix(ch1) {
|
|
||||||
return STOP_FLAG, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cfg.IsFlagPrefix(ch0) {
|
|
||||||
return SHORT_FLAG, str, pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.cfg.IsFlagPrefix(ch0) {
|
|
||||||
if s.cfg.IsFlagPrefix(ch1) {
|
|
||||||
return LONG_FLAG, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
return COMPOUND_SHORT_FLAG, str, pos
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDENT, str, pos
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
package argh
|
|
||||||
|
|
||||||
var (
|
|
||||||
// POSIXyScannerConfig defines a scanner config that uses '-'
|
|
||||||
// as the flag prefix, which also means that "--" is the "long
|
|
||||||
// flag" prefix, a bare "--" is considered STOP_FLAG, and a
|
|
||||||
// bare "-" is considered STDIN_FLAG.
|
|
||||||
POSIXyScannerConfig = &ScannerConfig{
|
|
||||||
AssignmentOperator: '=',
|
|
||||||
FlagPrefix: '-',
|
|
||||||
MultiValueDelim: ',',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ScannerConfig struct {
|
|
||||||
AssignmentOperator rune
|
|
||||||
FlagPrefix rune
|
|
||||||
MultiValueDelim rune
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ScannerConfig) IsFlagPrefix(ch rune) bool {
|
|
||||||
return ch == cfg.FlagPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ScannerConfig) IsMultiValueDelim(ch rune) bool {
|
|
||||||
return ch == cfg.MultiValueDelim
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ScannerConfig) IsAssignmentOperator(ch rune) bool {
|
|
||||||
return ch == cfg.AssignmentOperator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ScannerConfig) IsBlankspace(ch rune) bool {
|
|
||||||
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *ScannerConfig) IsUnderscore(ch rune) bool {
|
|
||||||
return ch == '_'
|
|
||||||
}
|
|
@@ -1,53 +0,0 @@
|
|||||||
//go:generate stringer -type Token
|
|
||||||
|
|
||||||
package argh
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ILLEGAL Token = iota
|
|
||||||
EOL
|
|
||||||
EMPTY // ''
|
|
||||||
BS // ' ' '\t' '\n'
|
|
||||||
IDENT // char group without flag prefix: 'some' 'words'
|
|
||||||
ARG_DELIMITER // rune(0)
|
|
||||||
ASSIGN // '='
|
|
||||||
MULTI_VALUE_DELIMITER // ','
|
|
||||||
LONG_FLAG // char group with double flag prefix: '--flag'
|
|
||||||
SHORT_FLAG // single char with single flag prefix: '-f'
|
|
||||||
COMPOUND_SHORT_FLAG // char group with single flag prefix: '-flag'
|
|
||||||
STDIN_FLAG // '-'
|
|
||||||
STOP_FLAG // '--'
|
|
||||||
|
|
||||||
nul = rune(0)
|
|
||||||
eol = rune(-1)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Token int
|
|
||||||
|
|
||||||
// Position is adapted from go/token.Position
|
|
||||||
type Position struct {
|
|
||||||
Column int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Position) IsValid() bool { return p.Column > 0 }
|
|
||||||
|
|
||||||
func (p Position) String() string {
|
|
||||||
s := ""
|
|
||||||
if p.IsValid() {
|
|
||||||
s = fmt.Sprintf("%d", p.Column)
|
|
||||||
}
|
|
||||||
if s == "" {
|
|
||||||
s = "-"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pos is borrowed from go/token.Pos
|
|
||||||
type Pos int
|
|
||||||
|
|
||||||
const NoPos Pos = 0
|
|
||||||
|
|
||||||
func (p Pos) IsValid() bool {
|
|
||||||
return p != NoPos
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
// Code generated by "stringer -type Token"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package argh
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
|
||||||
// Re-run the stringer command to generate them again.
|
|
||||||
var x [1]struct{}
|
|
||||||
_ = x[ILLEGAL-0]
|
|
||||||
_ = x[EOL-1]
|
|
||||||
_ = x[EMPTY-2]
|
|
||||||
_ = x[BS-3]
|
|
||||||
_ = x[IDENT-4]
|
|
||||||
_ = x[ARG_DELIMITER-5]
|
|
||||||
_ = x[ASSIGN-6]
|
|
||||||
_ = x[MULTI_VALUE_DELIMITER-7]
|
|
||||||
_ = x[LONG_FLAG-8]
|
|
||||||
_ = x[SHORT_FLAG-9]
|
|
||||||
_ = x[COMPOUND_SHORT_FLAG-10]
|
|
||||||
_ = x[STDIN_FLAG-11]
|
|
||||||
_ = x[STOP_FLAG-12]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _Token_name = "ILLEGALEOLEMPTYBSIDENTARG_DELIMITERASSIGNMULTI_VALUE_DELIMITERLONG_FLAGSHORT_FLAGCOMPOUND_SHORT_FLAGSTDIN_FLAGSTOP_FLAG"
|
|
||||||
|
|
||||||
var _Token_index = [...]uint8{0, 7, 10, 15, 17, 22, 35, 41, 62, 71, 81, 100, 110, 119}
|
|
||||||
|
|
||||||
func (i Token) String() string {
|
|
||||||
if i < 0 || i >= Token(len(_Token_index)-1) {
|
|
||||||
return "Token(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _Token_name[_Token_index[i]:_Token_index[i+1]]
|
|
||||||
}
|
|
1
lcthw/.envrc
Normal file
1
lcthw/.envrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export CFLAGS='-Werror -g'
|
3
lcthw/.ex10.argv
Normal file
3
lcthw/.ex10.argv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
carrot
|
||||||
|
angelfood
|
||||||
|
yellow
|
5
lcthw/.ex13.argv
Normal file
5
lcthw/.ex13.argv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
garble
|
||||||
|
geckle
|
||||||
|
gimple
|
||||||
|
gorble
|
||||||
|
gunkle
|
1
lcthw/.gdbinit
Normal file
1
lcthw/.gdbinit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
set debuginfod enabled off
|
9
lcthw/.gitignore
vendored
Normal file
9
lcthw/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
ex1
|
||||||
|
ex3
|
||||||
|
ex7
|
||||||
|
ex8
|
||||||
|
ex9
|
||||||
|
ex10
|
||||||
|
ex11
|
||||||
|
ex12
|
||||||
|
ex13
|
35
lcthw/Makefile
Normal file
35
lcthw/Makefile
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
CFLAGS ?= -Wall -g
|
||||||
|
GDBRUN = gdb --batch --ex run --ex bt --ex q --args
|
||||||
|
BUILD_TARGETS = $(foreach ex,$(wildcard ex*.c),$(subst .c,,$(ex)))
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: build test
|
||||||
|
|
||||||
|
.PHONY: echo
|
||||||
|
echo:
|
||||||
|
@echo BUILD_TARGETS=$(BUILD_TARGETS)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -f $(BUILD_TARGETS)
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: $(BUILD_TARGETS)
|
||||||
|
|
||||||
|
.PHONY: gtest
|
||||||
|
gtest:
|
||||||
|
@$(foreach bt, $(BUILD_TARGETS), make .gtest.$(bt) &&) \
|
||||||
|
printf '\ngYAY\n'
|
||||||
|
|
||||||
|
.gtest.%:
|
||||||
|
@test_argv=$$(cat .$*.argv 2>/dev/null || echo) && \
|
||||||
|
printf '\n==> %s\n' "$*" && $(GDBRUN) ./$*
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
@$(foreach bt, $(BUILD_TARGETS), make .test.$(bt) &&) \
|
||||||
|
printf '\nYAY\n'
|
||||||
|
|
||||||
|
.test.%:
|
||||||
|
@test_argv=$$(cat .$*.argv 2>/dev/null || echo) && \
|
||||||
|
printf '\n==> %s\n' "$*" && ./$* $$test_argv
|
12
lcthw/ex1.c
Normal file
12
lcthw/ex1.c
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* This is a comment. */
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int distance = 100;
|
||||||
|
|
||||||
|
// this is also a comment
|
||||||
|
printf("You are %d miles away.\n", distance);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
62
lcthw/ex10.c
Normal file
62
lcthw/ex10.c
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("ERROR: You need at least one argument.\n");
|
||||||
|
// this is how you abort a program
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int arg = 1;
|
||||||
|
for (arg = 1; arg < argc; arg++) {
|
||||||
|
for (i = 0; argv[arg][i] != '\0'; i++) {
|
||||||
|
printf("(%d) ", arg);
|
||||||
|
|
||||||
|
char letter = argv[arg][i];
|
||||||
|
|
||||||
|
// lowercase letters
|
||||||
|
if (letter >= 'A' && letter <= 'Z') {
|
||||||
|
letter += ('a' - 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (letter) {
|
||||||
|
case 'a':
|
||||||
|
printf("%d: 'a'\n", i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
printf("%d: 'e'\n", i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
printf("%d: 'i'\n", i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
printf("%d: 'o'\n", i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'u':
|
||||||
|
printf("%d: 'u'\n", i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'y':
|
||||||
|
// why i > 2? is this a bug?
|
||||||
|
if (i > 2) {
|
||||||
|
// it's only sometimes Y
|
||||||
|
printf("%d: 'y'\n", i);
|
||||||
|
} else {
|
||||||
|
printf("%d: %c is not a vowel\n", i, letter);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("%d: %c is not a vowel\n", i, letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
48
lcthw/ex11.c
Normal file
48
lcthw/ex11.c
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int numbers[4] = { 0 };
|
||||||
|
char name[4] = { 'a' };
|
||||||
|
|
||||||
|
// first, print them out raw
|
||||||
|
printf("numbers: %d %d %d %d\n",
|
||||||
|
numbers[0], numbers[1], numbers[2], numbers[3]);
|
||||||
|
|
||||||
|
printf("name each: %c %c %c %c\n",
|
||||||
|
name[0], name[1], name[2], name[3]);
|
||||||
|
|
||||||
|
printf("name: %s\n", name);
|
||||||
|
|
||||||
|
// setup the numbers
|
||||||
|
numbers[0] = 1;
|
||||||
|
numbers[1] = 2;
|
||||||
|
numbers[2] = 3;
|
||||||
|
numbers[3] = 4;
|
||||||
|
|
||||||
|
// setup the name
|
||||||
|
name[0] = 'Z';
|
||||||
|
name[1] = 'e';
|
||||||
|
name[2] = 'd';
|
||||||
|
name[3] = '\0';
|
||||||
|
|
||||||
|
// then print them out initialized
|
||||||
|
printf("numbers: %d %d %d %d\n",
|
||||||
|
numbers[0], numbers[1], numbers[2], numbers[3]);
|
||||||
|
|
||||||
|
printf("name each: %c %c %c %c\n",
|
||||||
|
name[0], name[1], name[2], name[3]);
|
||||||
|
|
||||||
|
// print the name like a string
|
||||||
|
printf("name: %s\n", name);
|
||||||
|
|
||||||
|
// another way to use name
|
||||||
|
char *another = "Zed";
|
||||||
|
|
||||||
|
printf("another: %s\n", another);
|
||||||
|
|
||||||
|
printf("another each: %c %c %c %c\n",
|
||||||
|
another[0], another[1], another[2], another[3]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
32
lcthw/ex12.c
Normal file
32
lcthw/ex12.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int areas[] = { 10, 12, 13, 14, 20 };
|
||||||
|
char name[] = "Zed";
|
||||||
|
char full_name[] = {
|
||||||
|
'Z', 'e', 'd',
|
||||||
|
' ', 'A', '.', ' ',
|
||||||
|
'S', 'h', 'a', 'w', '\0',
|
||||||
|
};
|
||||||
|
|
||||||
|
// WARNING: On some systems you may have to change the
|
||||||
|
// %ld in this code to a %u since it will use unsigned ints
|
||||||
|
printf("The size of an int: %ld\n", sizeof(int));
|
||||||
|
printf("The size of areas (int[]): %ld\n", sizeof(areas));
|
||||||
|
printf("The number of ints in areas: %ld\n",
|
||||||
|
sizeof(areas) / sizeof(int));
|
||||||
|
printf("The first area is %d, the 2nd %d.\n", areas[0], areas[1]);
|
||||||
|
|
||||||
|
printf("The size of a char: %ld\n", sizeof(char));
|
||||||
|
printf("The size of name (char[]): %ld\n", sizeof(name));
|
||||||
|
printf("The number of chars: %ld\n", sizeof(name) / sizeof(char));
|
||||||
|
|
||||||
|
printf("The size of full_name (char[]): %ld\n", sizeof(full_name));
|
||||||
|
printf("The number of chars: %ld\n",
|
||||||
|
sizeof(full_name) / sizeof(char));
|
||||||
|
|
||||||
|
printf("name=\"%s\" and full_name=\"%s\"\n", name, full_name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
26
lcthw/ex13.c
Normal file
26
lcthw/ex13.c
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
// go through each string in argv
|
||||||
|
// why am I skipping argv[0]?
|
||||||
|
for (i = 1; i < argc; i++) {
|
||||||
|
printf("arg %d: %s\n", i, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's make our own array of strings
|
||||||
|
char *states[] = {
|
||||||
|
"California",
|
||||||
|
"Oregon",
|
||||||
|
"Washington",
|
||||||
|
"Texas",
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (i = 0; states[i] != NULL; i++) {
|
||||||
|
printf("state %d: %s\n", i, states[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
12
lcthw/ex3.c
Normal file
12
lcthw/ex3.c
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int age = 10;
|
||||||
|
int height = 72;
|
||||||
|
|
||||||
|
printf("I am %d years old.\n", age);
|
||||||
|
printf("I am %d inches tall.\n", height);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
43
lcthw/ex7.c
Normal file
43
lcthw/ex7.c
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
int distance = 100;
|
||||||
|
float power = 2.345f;
|
||||||
|
double super_power = 56789.4532;
|
||||||
|
char initial = 'A';
|
||||||
|
char first_name[] = "Zed";
|
||||||
|
char last_name[] = "Shaw";
|
||||||
|
|
||||||
|
printf("You are %d miles away.\n", distance);
|
||||||
|
printf("You have %f levels of power.\n", power);
|
||||||
|
printf("You have %f awesome super powers.\n", super_power);
|
||||||
|
printf("I have an initial %c.\n", initial);
|
||||||
|
printf("I have a first name %s.\n", first_name);
|
||||||
|
printf("I have a last name %s.\n", last_name);
|
||||||
|
printf("My whole name is %s %c. %s.\n",
|
||||||
|
first_name, initial, last_name);
|
||||||
|
|
||||||
|
int bugs = 100;
|
||||||
|
double bug_rate = 1.2;
|
||||||
|
|
||||||
|
printf("You have %d bugs at the imaginary rate of %f.\n",
|
||||||
|
bugs, bug_rate);
|
||||||
|
|
||||||
|
long universe_of_defects = 1L * 1024L * 1024L * 1024L;
|
||||||
|
printf("The entire universe has %ld bugs.\n", universe_of_defects);
|
||||||
|
|
||||||
|
double expected_bugs = bugs * bug_rate;
|
||||||
|
printf("You are expected to have %f bugs.\n", expected_bugs);
|
||||||
|
|
||||||
|
double part_of_universe = expected_bugs / universe_of_defects;
|
||||||
|
printf("That is only a %e portion of the universe.\n",
|
||||||
|
part_of_universe);
|
||||||
|
|
||||||
|
// this makes no sense, just a demo of something weird
|
||||||
|
char nul_byte = '\0';
|
||||||
|
int care_percentage = bugs * nul_byte;
|
||||||
|
printf("Which means you should care %d%%.\n", care_percentage);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
21
lcthw/ex8.c
Normal file
21
lcthw/ex8.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (argc == 1) {
|
||||||
|
printf("You have no argument.\n");
|
||||||
|
} else if (argc > 1 && argc < 4) {
|
||||||
|
printf("Here's your arguments:\n");
|
||||||
|
|
||||||
|
for (i = 0; i < argc; i++) {
|
||||||
|
printf("%s ", argv[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
} else {
|
||||||
|
printf("You have too many arguments.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
15
lcthw/ex9.c
Normal file
15
lcthw/ex9.c
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int i = 25;
|
||||||
|
while (i > 0) {
|
||||||
|
printf("%d", i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need this to add a final newline
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
1
leetcode/.gitignore
vendored
Normal file
1
leetcode/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.envrc
|
1
leetcode/.python-version
Normal file
1
leetcode/.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12.0
|
1
leetcode/.testdata/max_sub_array0.json
Normal file
1
leetcode/.testdata/max_sub_array0.json
Normal file
File diff suppressed because one or more lines are too long
147
leetcode/pyproject.toml
Normal file
147
leetcode/pyproject.toml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "leetcode"
|
||||||
|
description = 'What A Mess'
|
||||||
|
readme = "README.md"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
license = "MIT"
|
||||||
|
keywords = []
|
||||||
|
authors = [
|
||||||
|
{ name = "Dan Buch", email = "dan@meatballhat.com" },
|
||||||
|
]
|
||||||
|
classifiers = []
|
||||||
|
dependencies = [
|
||||||
|
"ipython",
|
||||||
|
"ipdb",
|
||||||
|
"matplotlib",
|
||||||
|
"numpy"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.envs.default]
|
||||||
|
dependencies = [
|
||||||
|
"coverage[toml]>=6.5",
|
||||||
|
"pytest",
|
||||||
|
]
|
||||||
|
[tool.hatch.envs.default.scripts]
|
||||||
|
test = "pytest {args:tests}"
|
||||||
|
test-cov = "coverage run -m pytest {args:tests}"
|
||||||
|
cov-report = [
|
||||||
|
"- coverage combine",
|
||||||
|
"coverage report",
|
||||||
|
]
|
||||||
|
cov = [
|
||||||
|
"test-cov",
|
||||||
|
"cov-report",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[tool.hatch.envs.all.matrix]]
|
||||||
|
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||||
|
|
||||||
|
[tool.hatch.envs.lint]
|
||||||
|
detached = true
|
||||||
|
dependencies = [
|
||||||
|
"black>=23.1.0",
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"ruff>=0.0.243",
|
||||||
|
]
|
||||||
|
[tool.hatch.envs.lint.scripts]
|
||||||
|
typing = "mypy --install-types --non-interactive {args:.}"
|
||||||
|
style = [
|
||||||
|
"ruff {args:.}",
|
||||||
|
"black --check --diff {args:.}",
|
||||||
|
]
|
||||||
|
fmt = [
|
||||||
|
"black {args:.}",
|
||||||
|
"ruff --fix {args:.}",
|
||||||
|
"style",
|
||||||
|
]
|
||||||
|
all = [
|
||||||
|
"style",
|
||||||
|
"typing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
target-version = ["py37"]
|
||||||
|
line-length = 90
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py37"
|
||||||
|
line-length = 90
|
||||||
|
select = [
|
||||||
|
"A",
|
||||||
|
"ARG",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"DTZ",
|
||||||
|
"E",
|
||||||
|
"EM",
|
||||||
|
"F",
|
||||||
|
"FBT",
|
||||||
|
"I",
|
||||||
|
"ICN",
|
||||||
|
"ISC",
|
||||||
|
"N",
|
||||||
|
"PLC",
|
||||||
|
"PLE",
|
||||||
|
"PLR",
|
||||||
|
"PLW",
|
||||||
|
"Q",
|
||||||
|
"RUF",
|
||||||
|
"S",
|
||||||
|
"T",
|
||||||
|
"TID",
|
||||||
|
"UP",
|
||||||
|
"W",
|
||||||
|
"YTT",
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
# Allow non-abstract empty methods in abstract base classes
|
||||||
|
"B027",
|
||||||
|
# Allow boolean positional values in function calls, like `dict.get(... True)`
|
||||||
|
"FBT003",
|
||||||
|
# Ignore checks for possible passwords
|
||||||
|
"S105", "S106", "S107",
|
||||||
|
# Ignore complexity
|
||||||
|
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
|
||||||
|
# Allow print func
|
||||||
|
"T201",
|
||||||
|
# Allow assert statements
|
||||||
|
"S101",
|
||||||
|
]
|
||||||
|
unfixable = [
|
||||||
|
# Don't touch unused imports
|
||||||
|
"F401",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.isort]
|
||||||
|
known-first-party = ["leetcode"]
|
||||||
|
|
||||||
|
[tool.ruff.flake8-tidy-imports]
|
||||||
|
ban-relative-imports = "all"
|
||||||
|
|
||||||
|
[tool.ruff.per-file-ignores]
|
||||||
|
# Tests can use magic values, assertions, and relative imports
|
||||||
|
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source_pkgs = ["leetcode", "tests"]
|
||||||
|
branch = true
|
||||||
|
parallel = true
|
||||||
|
omit = [
|
||||||
|
"src/leetcode/__about__.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.paths]
|
||||||
|
leetcode = ["src/leetcode", "*/leetcode/src/leetcode"]
|
||||||
|
tests = ["tests", "*/leetcode/tests"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"no cov",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
57
leetcode/roman.py
Normal file
57
leetcode/roman.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
SIMPLE = {
|
||||||
|
"I": 1,
|
||||||
|
"V": 5,
|
||||||
|
"X": 10,
|
||||||
|
"L": 50,
|
||||||
|
"C": 100,
|
||||||
|
"D": 500,
|
||||||
|
"M": 1000,
|
||||||
|
}
|
||||||
|
SIMPLE_REVERSE = {v: k for k, v in SIMPLE.items()}
|
||||||
|
COMPOUND = {
|
||||||
|
"IV": 4,
|
||||||
|
"IX": 9,
|
||||||
|
"XL": 40,
|
||||||
|
"XC": 90,
|
||||||
|
"CD": 400,
|
||||||
|
"CM": 900,
|
||||||
|
}
|
||||||
|
PREFIXES = {k[0] for k in COMPOUND.keys()}
|
||||||
|
COMPOUND_REVERSE = {v: k for k, v in COMPOUND.items()}
|
||||||
|
ALL = SIMPLE | COMPOUND
|
||||||
|
ALL_REVERSE = {v: k for k, v in ALL.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def i2r(i: int) -> str:
|
||||||
|
if i > 100_000:
|
||||||
|
raise ValueError(f"{i} is too silly")
|
||||||
|
|
||||||
|
r: list[str] = []
|
||||||
|
|
||||||
|
for int_val, roman_val in sorted(ALL_REVERSE.items(), reverse=True):
|
||||||
|
remainder = i % int_val
|
||||||
|
|
||||||
|
r += [roman_val] * int((i - remainder) / int_val)
|
||||||
|
|
||||||
|
i = remainder
|
||||||
|
|
||||||
|
return "".join(r)
|
||||||
|
|
||||||
|
|
||||||
|
def r2i(r: str) -> int:
|
||||||
|
total = 0
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
for i in range(len(r)):
|
||||||
|
if i + offset > len(r) - 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
c = r[i + offset]
|
||||||
|
if c in PREFIXES and (i + offset + 1) < len(r) and c + r[i + offset + 1] in ALL:
|
||||||
|
total += ALL[c + r[i + offset + 1]]
|
||||||
|
offset += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
total += ALL[c]
|
||||||
|
|
||||||
|
return total
|
57
leetcode/spiral_matrix.py
Normal file
57
leetcode/spiral_matrix.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import itertools
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
def matrix_spiral(matrix: list[list[typing.Any]]) -> list[typing.Any]:
|
||||||
|
return [matrix[y][x] for x, y in matrix_spiral_path(matrix)]
|
||||||
|
|
||||||
|
|
||||||
|
def matrix_spiral_path(matrix: list[list[int]]) -> list[tuple[int, int]]:
|
||||||
|
snek = SpinSnek(matrix)
|
||||||
|
|
||||||
|
while snek.step():
|
||||||
|
...
|
||||||
|
|
||||||
|
return snek.path
|
||||||
|
|
||||||
|
|
||||||
|
COMPASS = (
|
||||||
|
(1, 0), # east
|
||||||
|
(0, 1), # south
|
||||||
|
(-1, 0), # west
|
||||||
|
(0, -1), # north
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SpinSnek:
|
||||||
|
def __init__(self, board: list[list[int]], loc: tuple[int, int] = (0, 0)):
|
||||||
|
self.max_loc: tuple[int, int] = (len(board[0]) - 1, len(board) - 1)
|
||||||
|
self.spinner: itertools.cycle[tuple[int, int]] = itertools.cycle(COMPASS)
|
||||||
|
self.direction = next(self.spinner)
|
||||||
|
self.path: list[tuple[int, int]] = [loc]
|
||||||
|
self.missteps: int = 0
|
||||||
|
|
||||||
|
def step(self) -> bool:
|
||||||
|
loc = self.path[-1]
|
||||||
|
next_loc: tuple[int, int] = (
|
||||||
|
loc[0] + self.direction[0],
|
||||||
|
loc[1] + self.direction[1],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
next_loc[0] > self.max_loc[0]
|
||||||
|
or next_loc[1] > self.max_loc[1]
|
||||||
|
or next_loc[0] < 0
|
||||||
|
or next_loc[1] < 0
|
||||||
|
or next_loc in self.path
|
||||||
|
):
|
||||||
|
self.direction = next(self.spinner)
|
||||||
|
if self.missteps > len(COMPASS) - 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.missteps += 1
|
||||||
|
return self.step()
|
||||||
|
|
||||||
|
self.missteps: int = 0
|
||||||
|
self.path.append(next_loc)
|
||||||
|
return True
|
169
leetcode/stdlib.py
Normal file
169
leetcode/stdlib.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import dataclasses
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
class LinkedListNode(typing.Protocol):
|
||||||
|
val: int
|
||||||
|
next: typing.Optional["LinkedListNode"]
|
||||||
|
|
||||||
|
|
||||||
|
class ListNode:
|
||||||
|
"""ListNode is the leetcode "standard library" type used in linked lists"""
|
||||||
|
|
||||||
|
def __init__(self, val: int = 0, next: typing.Optional["ListNode"] = None): # no qa
|
||||||
|
self.val = val
|
||||||
|
self.next = next
|
||||||
|
|
||||||
|
|
||||||
|
class ListNodeRandom:
|
||||||
|
"""ListNodeRandom is another weirdo linked list thing from
|
||||||
|
leetcode that is obviously very different than a binary tree
|
||||||
|
node :upside_down_face:
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
x: int,
|
||||||
|
next: typing.Optional["ListNodeRandom"] = None,
|
||||||
|
random: typing.Optional["ListNodeRandom"] = None,
|
||||||
|
):
|
||||||
|
self.val = x
|
||||||
|
self.next = next
|
||||||
|
self.random = random
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class ListNodeRandomNicely:
|
||||||
|
val: int
|
||||||
|
next: typing.Optional["ListNodeRandomNicely"] = None
|
||||||
|
random: typing.Optional["ListNodeRandomNicely"] = None
|
||||||
|
|
||||||
|
|
||||||
|
class BinaryTreeNode(typing.Protocol):
|
||||||
|
val: int
|
||||||
|
left: typing.Optional["BinaryTreeNode"]
|
||||||
|
right: typing.Optional["BinaryTreeNode"]
|
||||||
|
|
||||||
|
|
||||||
|
class TreeNode:
|
||||||
|
"""TreeNode is the leetcode "standard library" type used in binary trees"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
val: int = 0,
|
||||||
|
left: typing.Optional["TreeNode"] = None,
|
||||||
|
right: typing.Optional["TreeNode"] = None,
|
||||||
|
):
|
||||||
|
self.val = val
|
||||||
|
self.left = left
|
||||||
|
self.right = right
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_int(cls, val: int | None) -> typing.Optional["TreeNode"]:
|
||||||
|
if val is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return TreeNode(val)
|
||||||
|
|
||||||
|
# __repr__ was added by me
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
filtered_parts = []
|
||||||
|
|
||||||
|
for key, value in [
|
||||||
|
("val", self.val),
|
||||||
|
("left", self.left),
|
||||||
|
("right", self.right),
|
||||||
|
]:
|
||||||
|
if value is not None:
|
||||||
|
filtered_parts.append((key, value))
|
||||||
|
|
||||||
|
middle = ", ".join([f"{k}={v!r}" for k, v in filtered_parts])
|
||||||
|
|
||||||
|
return f"TreeNode({middle})"
|
||||||
|
|
||||||
|
# __eq__ was added by me
|
||||||
|
def __eq__(self, other: typing.Optional["TreeNode"]) -> bool:
|
||||||
|
return (
|
||||||
|
other is not None
|
||||||
|
and self.val == other.val
|
||||||
|
and self.left == other.left
|
||||||
|
and self.right == other.right
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectableBinaryTreeNode(typing.Protocol):
|
||||||
|
val: int
|
||||||
|
left: typing.Optional["BinaryTreeNode"]
|
||||||
|
right: typing.Optional["BinaryTreeNode"]
|
||||||
|
next: typing.Optional["BinaryTreeNode"]
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
"""Node is the *other* leetcode "standard library" type used in binary trees"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
val: int = 0,
|
||||||
|
left: typing.Optional["Node"] = None,
|
||||||
|
right: typing.Optional["Node"] = None,
|
||||||
|
next: typing.Optional["Node"] = None, # no qa
|
||||||
|
):
|
||||||
|
self.val = val
|
||||||
|
self.left = left
|
||||||
|
self.right = right
|
||||||
|
self.next = next
|
||||||
|
|
||||||
|
# __repr__ was added by me
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
filtered_parts = []
|
||||||
|
|
||||||
|
for key, value in [
|
||||||
|
("val", self.val),
|
||||||
|
("right", self.right),
|
||||||
|
("left", self.left),
|
||||||
|
("next", self.next),
|
||||||
|
]:
|
||||||
|
if value is not None:
|
||||||
|
filtered_parts.append((key, value))
|
||||||
|
|
||||||
|
middle = ", ".join([f"{k}={v!r}" for k, v in filtered_parts])
|
||||||
|
|
||||||
|
return f"Node({middle})"
|
||||||
|
|
||||||
|
# __eq__ was added by me
|
||||||
|
def __eq__(self, other: "Node") -> bool:
|
||||||
|
return (
|
||||||
|
other is not None
|
||||||
|
and self.val == other.val
|
||||||
|
and self.left == other.left
|
||||||
|
and self.right == other.right
|
||||||
|
and self.next == other.next
|
||||||
|
)
|
||||||
|
|
||||||
|
# __list__ was added by me
|
||||||
|
def __list__(self) -> list[int | None]:
|
||||||
|
ret = [self.val]
|
||||||
|
ret += self.right.__list__() if self.right is not None else [None]
|
||||||
|
ret += self.left.__list__() if self.left is not None else [None]
|
||||||
|
ret += self.next.__list__() if self.next is not None else [None]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class NeighborlyNode:
|
||||||
|
"""NeighborlyNode is a "Node" type used in leetcode graph puzzles"""
|
||||||
|
|
||||||
|
def __init__(self, val=0, neighbors=None):
|
||||||
|
self.val = val
|
||||||
|
self.neighbors = neighbors if neighbors is not None else []
|
||||||
|
|
||||||
|
|
||||||
|
class NeighborlyNodeNicely(typing.NamedTuple):
|
||||||
|
val: int
|
||||||
|
neighbors: list["NeighborlyNodeNicely"]
|
||||||
|
|
||||||
|
def __eq__(self, other: typing.Optional["NeighborlyNodeNicely"]) -> bool:
|
||||||
|
return (
|
||||||
|
other is not None
|
||||||
|
and self.val == other.val
|
||||||
|
and [n.val for n in self.neighbors] == [n.val for n in other.neighbors]
|
||||||
|
)
|
640
leetcode/stuff.py
Normal file
640
leetcode/stuff.py
Normal file
@@ -0,0 +1,640 @@
|
|||||||
|
import collections.abc
|
||||||
|
import copy
|
||||||
|
import random
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import stdlib
|
||||||
|
|
||||||
|
|
||||||
|
def yep(s: str) -> bool:
|
||||||
|
return s.strip().lower().startswith("y")
|
||||||
|
|
||||||
|
|
||||||
|
def guess_bisect_repl(lower: int, upper: int) -> int:
|
||||||
|
mid = lower + ((upper - lower) // 2)
|
||||||
|
|
||||||
|
if yep(input(f"is it {mid}? ")):
|
||||||
|
return mid
|
||||||
|
|
||||||
|
if yep(input(f"higher than {mid}? ")):
|
||||||
|
return guess_bisect_repl(mid, upper)
|
||||||
|
|
||||||
|
return guess_bisect_repl(lower, mid)
|
||||||
|
|
||||||
|
|
||||||
|
def find_sqrt_ish(n: int) -> int:
|
||||||
|
return int(find_bisect(0, n, gen_sqrt_check(n)))
|
||||||
|
|
||||||
|
|
||||||
|
def gen_sqrt_check(n: int) -> typing.Callable[[float], int]:
|
||||||
|
def check(mid: float) -> int:
|
||||||
|
mid_sq: float = mid * mid
|
||||||
|
|
||||||
|
if mid_sq == n:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if mid_sq < n:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
return check
|
||||||
|
|
||||||
|
|
||||||
|
def find_bisect(
|
||||||
|
lower: float, upper: float, check: typing.Callable[[float], int]
|
||||||
|
) -> float:
|
||||||
|
mid: float = lower + ((upper - lower) / 2)
|
||||||
|
|
||||||
|
print(f"lower={lower} mid={mid} upper={upper}")
|
||||||
|
|
||||||
|
if mid == lower or mid == upper or check(mid) == 0:
|
||||||
|
return mid
|
||||||
|
|
||||||
|
if check(mid) == 1:
|
||||||
|
return find_bisect(mid, upper, check)
|
||||||
|
|
||||||
|
return find_bisect(lower, mid, check)
|
||||||
|
|
||||||
|
|
||||||
|
def cartesian_path(p0: tuple[int, int], p1: tuple[int, int]) -> list[tuple[int, int]]:
|
||||||
|
path: list[tuple[int, int]] = []
|
||||||
|
|
||||||
|
if p0 < p1:
|
||||||
|
for i in range(p0[1], p1[1]):
|
||||||
|
path.append((i, p0[0]))
|
||||||
|
|
||||||
|
for i in range(p0[0], p1[0]):
|
||||||
|
path.append((p1[1], i))
|
||||||
|
|
||||||
|
else:
|
||||||
|
for i in range(p0[1], p1[1] - 1, -1):
|
||||||
|
path.append((i, p0[0]))
|
||||||
|
|
||||||
|
for i in range(p0[0] - 1, p1[0], -1):
|
||||||
|
path.append((p1[1], i))
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def gen_matrix(width: int, height: int) -> list[list[int]]:
|
||||||
|
return [list(range(width)) for _ in range(height)]
|
||||||
|
|
||||||
|
|
||||||
|
class MinStack:
|
||||||
|
def __init__(self):
|
||||||
|
self._v: list[int] = []
|
||||||
|
self._min: list[int] = []
|
||||||
|
|
||||||
|
def push(self, val: int) -> None:
|
||||||
|
self._v.append(val)
|
||||||
|
self._min.append(min(val, self._min[-1] if self._min else val))
|
||||||
|
|
||||||
|
def pop(self) -> None:
|
||||||
|
self._v.pop(-1)
|
||||||
|
self._min.pop(-1)
|
||||||
|
|
||||||
|
def top(self) -> int:
|
||||||
|
return self._v[-1]
|
||||||
|
|
||||||
|
def getMin(self) -> int: # no qa
|
||||||
|
return self._min[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def linked_list_to_list(head: stdlib.LinkedListNode | None) -> list[int]:
|
||||||
|
seen: set[int] = set()
|
||||||
|
ret: list[int] = []
|
||||||
|
|
||||||
|
while head is not None:
|
||||||
|
if hash(head) in seen:
|
||||||
|
return ret
|
||||||
|
|
||||||
|
seen.add(hash(head))
|
||||||
|
ret.append(head.val)
|
||||||
|
head = head.next
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def sort_linked_list(head: stdlib.LinkedListNode | None) -> stdlib.LinkedListNode | None:
|
||||||
|
by_val: list[tuple[int, stdlib.LinkedListNode]] = []
|
||||||
|
ret: stdlib.LinkedListNode | None = None
|
||||||
|
|
||||||
|
while head is not None:
|
||||||
|
by_val.append((head.val, head))
|
||||||
|
head = head.next
|
||||||
|
|
||||||
|
cur = ret
|
||||||
|
|
||||||
|
for _, node in sorted(by_val, key=lambda v: v[0]):
|
||||||
|
if cur is None:
|
||||||
|
cur = ret = node
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur.next = node
|
||||||
|
cur = cur.next
|
||||||
|
|
||||||
|
if cur is not None:
|
||||||
|
cur.next = None
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def connect_binary_tree_right(
|
||||||
|
root: stdlib.ConnectableBinaryTreeNode | None,
|
||||||
|
) -> tuple[stdlib.ConnectableBinaryTreeNode | None, list[int | None]]:
|
||||||
|
if root is None:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
by_level = binary_tree_by_level(copy.deepcopy(root))
|
||||||
|
by_level = typing.cast(dict[int, list[stdlib.ConnectableBinaryTreeNode]], by_level)
|
||||||
|
serialized: list[int | None] = []
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if 0 not in by_level or len(by_level[0]) == 0:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
connected_root = by_level[0][0]
|
||||||
|
|
||||||
|
for level, nodes in sorted(by_level.items(), key=lambda p: p[0]):
|
||||||
|
for i in range(len(nodes)):
|
||||||
|
serialized.append(nodes[i].val)
|
||||||
|
|
||||||
|
if len(nodes) > i + 1:
|
||||||
|
print(f"{'-' * level}> connecting {nodes[i].val} -> {nodes[i + 1].val}")
|
||||||
|
nodes[i].next = nodes[i + 1]
|
||||||
|
|
||||||
|
serialized.append(None)
|
||||||
|
|
||||||
|
return connected_root, serialized
|
||||||
|
|
||||||
|
|
||||||
|
def binary_tree_by_level(
|
||||||
|
root: stdlib.BinaryTreeNode,
|
||||||
|
) -> dict[int, list[stdlib.BinaryTreeNode]]:
|
||||||
|
combined: dict[int, list[stdlib.BinaryTreeNode]] = {}
|
||||||
|
|
||||||
|
for path in collect_binary_tree_levels(0, root):
|
||||||
|
level, node = path
|
||||||
|
combined.setdefault(level, [])
|
||||||
|
combined[level].insert(0, node)
|
||||||
|
|
||||||
|
return combined
|
||||||
|
|
||||||
|
|
||||||
|
def collect_binary_tree_levels(
|
||||||
|
level: int, node: stdlib.BinaryTreeNode | None
|
||||||
|
) -> typing.Iterator[tuple[int, stdlib.BinaryTreeNode]]:
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
yield (level, node)
|
||||||
|
yield from collect_binary_tree_levels(level + 1, node.right)
|
||||||
|
yield from collect_binary_tree_levels(level + 1, node.left)
|
||||||
|
|
||||||
|
|
||||||
|
def sum_binary_tree_path_ints(root: stdlib.BinaryTreeNode | None) -> int:
|
||||||
|
path_ints: list[int] = []
|
||||||
|
|
||||||
|
for path in collect_binary_tree_paths(root):
|
||||||
|
path_ints.append(int("".join([str(node.val) for node in path])))
|
||||||
|
|
||||||
|
return sum(path_ints)
|
||||||
|
|
||||||
|
|
||||||
|
def binary_tree_paths_as_lists(
|
||||||
|
paths: list[list[stdlib.BinaryTreeNode]],
|
||||||
|
) -> list[list[int]]:
|
||||||
|
paths_vals: list[list[int]] = []
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
paths_vals.append([node.val for node in path])
|
||||||
|
|
||||||
|
return paths_vals
|
||||||
|
|
||||||
|
|
||||||
|
def collect_binary_tree_paths(
|
||||||
|
node: stdlib.BinaryTreeNode | None,
|
||||||
|
) -> typing.Iterator[list[stdlib.BinaryTreeNode]]:
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if node.right is None and node.left is None:
|
||||||
|
yield [node]
|
||||||
|
return
|
||||||
|
|
||||||
|
if node.right is not None:
|
||||||
|
for path in collect_binary_tree_paths(node.right):
|
||||||
|
yield [node] + path
|
||||||
|
|
||||||
|
if node.left is not None:
|
||||||
|
for path in collect_binary_tree_paths(node.left):
|
||||||
|
yield [node] + path
|
||||||
|
|
||||||
|
|
||||||
|
def binary_tree_from_list(inlist: list[int | None]) -> stdlib.BinaryTreeNode | None:
|
||||||
|
if len(inlist) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
nodes: list[stdlib.BinaryTreeNode | None] = [
|
||||||
|
typing.cast(stdlib.BinaryTreeNode | None, stdlib.TreeNode.from_int(i))
|
||||||
|
for i in inlist
|
||||||
|
]
|
||||||
|
nodes_copy = nodes[::-1]
|
||||||
|
root = nodes_copy.pop()
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
if node is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(nodes_copy) == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
node.left = nodes_copy.pop()
|
||||||
|
|
||||||
|
if len(nodes_copy) > 0:
|
||||||
|
node.right = nodes_copy.pop()
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def binary_tree_from_preorder_inorder(
|
||||||
|
preorder: list[int], inorder: list[int]
|
||||||
|
) -> stdlib.BinaryTreeNode | None:
|
||||||
|
preorder_reversed = preorder[::-1]
|
||||||
|
|
||||||
|
def subtree(left: list[int], right: list[int]) -> stdlib.BinaryTreeNode:
|
||||||
|
root: stdlib.BinaryTreeNode = typing.cast(
|
||||||
|
stdlib.BinaryTreeNode, stdlib.TreeNode(preorder_reversed.pop())
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(left) > 1:
|
||||||
|
split_pos = left.index(preorder_reversed[-1])
|
||||||
|
root.left = subtree(left[:split_pos], left[split_pos + 1 :])
|
||||||
|
elif len(left) == 1:
|
||||||
|
preorder_reversed.remove(left[0])
|
||||||
|
root.left = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(left[0]))
|
||||||
|
|
||||||
|
if len(right) > 1:
|
||||||
|
split_pos = right.index(preorder_reversed[-1])
|
||||||
|
root.right = subtree(right[:split_pos], right[split_pos + 1 :])
|
||||||
|
elif len(right) == 1:
|
||||||
|
preorder_reversed.remove(right[0])
|
||||||
|
root.right = typing.cast(stdlib.BinaryTreeNode, stdlib.TreeNode(right[0]))
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
split_pos = inorder.index(preorder[0])
|
||||||
|
|
||||||
|
return subtree(inorder[:split_pos], inorder[split_pos + 1 :])
|
||||||
|
|
||||||
|
|
||||||
|
class JumpSpace(typing.NamedTuple):
|
||||||
|
pos: int
|
||||||
|
val: int
|
||||||
|
moves: list["JumpSpace"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_board(
|
||||||
|
cls, pos: int = 0, board: typing.Iterable[int] = ()
|
||||||
|
) -> typing.Optional["JumpSpace"]:
|
||||||
|
board = list(board)
|
||||||
|
|
||||||
|
if len(board) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
space = cls(pos, board[pos], [])
|
||||||
|
space.collect(board)
|
||||||
|
return space
|
||||||
|
|
||||||
|
def collect(self, board: list[int]) -> None:
|
||||||
|
del self.moves[:]
|
||||||
|
|
||||||
|
if self.pos > len(board) or len(board) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for n in range(self.pos + 1, self.pos + self.val + 1):
|
||||||
|
if n >= len(board):
|
||||||
|
break
|
||||||
|
|
||||||
|
self.moves.append(typing.cast(JumpSpace, JumpSpace.from_board(n, board)))
|
||||||
|
|
||||||
|
def jump_paths(self) -> list[list[int]]:
|
||||||
|
ret: list[list[int]] = [[self.pos]]
|
||||||
|
|
||||||
|
for next_space in self.moves:
|
||||||
|
for path in next_space.jump_paths():
|
||||||
|
ret.append([self.pos] + path)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def collect_complete_jump_paths_from_board(board: list[int]) -> list[list[int]]:
|
||||||
|
return [
|
||||||
|
p
|
||||||
|
for p in collect_jump_paths_from_board(board)
|
||||||
|
if len(p) > 0 and p[-1] >= len(board) - 1
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def collect_jump_paths_from_board(board: list[int]) -> list[list[int]]:
|
||||||
|
space = JumpSpace.from_board(0, board)
|
||||||
|
if space is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return space.jump_paths()
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: the expensive way goes like this
|
||||||
|
# complete_paths = collect_complete_jump_paths_from_board(board)
|
||||||
|
|
||||||
|
# if len(complete_paths) == 0:
|
||||||
|
# return -1
|
||||||
|
|
||||||
|
# return min([len(p) - 1 for p in complete_paths])
|
||||||
|
|
||||||
|
|
||||||
|
def count_min_jumps_from_board(board: list[int]) -> int:
|
||||||
|
return len(collect_min_jumps_from_board(board))
|
||||||
|
|
||||||
|
|
||||||
|
def collect_min_jumps_from_board(board: list[int]) -> list[int]:
|
||||||
|
if len(board) < 3:
|
||||||
|
return list(range(1, len(board)))
|
||||||
|
|
||||||
|
jumps: list[int] = []
|
||||||
|
range_begin: int = 0
|
||||||
|
val = board[range_begin]
|
||||||
|
range_end: int = range_begin + val + 1
|
||||||
|
|
||||||
|
while range_end < len(board):
|
||||||
|
potential_jumps = board[range_begin:range_end]
|
||||||
|
|
||||||
|
scored_jumps = [
|
||||||
|
(val + range_begin + i, val, range_begin + i)
|
||||||
|
for i, val in enumerate(potential_jumps)
|
||||||
|
]
|
||||||
|
_, val, space = max(scored_jumps)
|
||||||
|
|
||||||
|
jumps.append(space)
|
||||||
|
|
||||||
|
range_begin = space
|
||||||
|
range_end = range_begin + val + 1
|
||||||
|
|
||||||
|
return jumps + [len(board) - 1]
|
||||||
|
|
||||||
|
|
||||||
|
def h_index(citations: list[int]) -> int:
|
||||||
|
last_qualified = None
|
||||||
|
|
||||||
|
for i, citation_count in enumerate(list(sorted(citations, reverse=True))):
|
||||||
|
if citation_count >= i + 1:
|
||||||
|
last_qualified = i + 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
return last_qualified or 0
|
||||||
|
|
||||||
|
|
||||||
|
class SlowRandomizedSet:
|
||||||
|
def __init__(self):
|
||||||
|
self._i: set[int] = set()
|
||||||
|
|
||||||
|
def insert(self, val: int) -> bool:
|
||||||
|
ok = val not in self._i
|
||||||
|
self._i.add(val)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
def remove(self, val: int) -> bool:
|
||||||
|
if val in self._i:
|
||||||
|
self._i.remove(val)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getRandom(self) -> int:
|
||||||
|
return random.choice(list(self._i))
|
||||||
|
|
||||||
|
|
||||||
|
class RandomizedSet:
|
||||||
|
def __init__(self):
|
||||||
|
self._l: list[int] = []
|
||||||
|
self._m: dict[int, int] = {}
|
||||||
|
|
||||||
|
def insert(self, val: int) -> bool:
|
||||||
|
if val in self._m:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._m[val] = len(self._l)
|
||||||
|
self._l.append(val)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove(self, val: int) -> bool:
|
||||||
|
if val not in self._m:
|
||||||
|
return False
|
||||||
|
|
||||||
|
val_loc = self._m[val]
|
||||||
|
last_val = self._l[-1]
|
||||||
|
self._l[val_loc] = last_val
|
||||||
|
self._m[last_val] = val_loc
|
||||||
|
|
||||||
|
self._l.pop()
|
||||||
|
self._m.pop(val)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getRandom(self) -> int:
|
||||||
|
return random.choice(self._l)
|
||||||
|
|
||||||
|
|
||||||
|
class TrieNode(typing.NamedTuple):
|
||||||
|
value: str
|
||||||
|
kids: dict[str, "TrieNode"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_leaf(self) -> bool:
|
||||||
|
return "__self__" in self.kids
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def leaf(cls) -> "TrieNode":
|
||||||
|
return cls("__self__", {})
|
||||||
|
|
||||||
|
|
||||||
|
class Trie:
|
||||||
|
def __init__(self):
|
||||||
|
self._root_node = TrieNode("", {})
|
||||||
|
|
||||||
|
def insert(self, word: str) -> None:
|
||||||
|
if len(word) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_node = self._root_node
|
||||||
|
|
||||||
|
for prefix in [word[: i + 1] for i in range(len(word))]:
|
||||||
|
current_node.kids.setdefault(prefix, TrieNode(prefix, {}))
|
||||||
|
current_node = current_node.kids[prefix]
|
||||||
|
|
||||||
|
leaf = TrieNode.leaf()
|
||||||
|
current_node.kids[leaf.value] = leaf
|
||||||
|
|
||||||
|
def search(self, word: str) -> bool:
|
||||||
|
return self._has(word, prefix_ok=False)
|
||||||
|
|
||||||
|
def startsWith(self, prefix: str) -> bool:
|
||||||
|
return self._has(prefix, prefix_ok=True)
|
||||||
|
|
||||||
|
def _has(self, word: str, prefix_ok: bool) -> bool:
|
||||||
|
if len(word) == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
reverse_path = [word[: i + 1] for i in range(len(word))][::-1]
|
||||||
|
current_node = self._root_node
|
||||||
|
|
||||||
|
while reverse_path and current_node is not None:
|
||||||
|
current_node = current_node.kids.get(reverse_path.pop())
|
||||||
|
|
||||||
|
return (
|
||||||
|
current_node is not None
|
||||||
|
and (current_node.is_leaf or prefix_ok)
|
||||||
|
and current_node.value == word
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def count_factorial_trailing_zeroes(number: int) -> int:
|
||||||
|
divisor: int = 5
|
||||||
|
zeroes_count: int = 0
|
||||||
|
|
||||||
|
while divisor <= number:
|
||||||
|
zeroes_count += number // divisor
|
||||||
|
divisor *= 5
|
||||||
|
|
||||||
|
return zeroes_count
|
||||||
|
|
||||||
|
|
||||||
|
def copy_random_list(
|
||||||
|
head: stdlib.ListNodeRandom | None,
|
||||||
|
) -> stdlib.ListNodeRandom | None:
|
||||||
|
if head is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ordered = []
|
||||||
|
cur = head
|
||||||
|
while cur is not None:
|
||||||
|
ordered.append(cur)
|
||||||
|
cur = cur.next
|
||||||
|
|
||||||
|
ordered_copy = [stdlib.ListNodeRandom(entry.val) for entry in ordered]
|
||||||
|
|
||||||
|
hash_idx = {hash(n): i for i, n in enumerate(ordered)}
|
||||||
|
|
||||||
|
for i, entry in enumerate(ordered):
|
||||||
|
if i + 1 < len(ordered_copy):
|
||||||
|
ordered_copy[i].next = ordered_copy[i + 1]
|
||||||
|
|
||||||
|
if entry.random is not None:
|
||||||
|
ordered_copy[i].random = ordered_copy[hash_idx[hash(entry.random)]]
|
||||||
|
|
||||||
|
return ordered_copy[0]
|
||||||
|
|
||||||
|
|
||||||
|
def sum_max_sub_array(nums: list[int]) -> int:
|
||||||
|
mmax = last = prev = nums[0]
|
||||||
|
|
||||||
|
for i in range(1, len(nums)):
|
||||||
|
prev = nums[i] + last
|
||||||
|
last = max(nums[i], prev)
|
||||||
|
mmax = max(mmax, last)
|
||||||
|
|
||||||
|
return mmax
|
||||||
|
|
||||||
|
|
||||||
|
def sum_max_sub_array_i(nums: list[int]) -> tuple[int, int]:
|
||||||
|
mmax_i: int = 0
|
||||||
|
mmax = last = prev = nums[0]
|
||||||
|
|
||||||
|
for i in range(1, len(nums)):
|
||||||
|
prev = nums[i] + last
|
||||||
|
last = max(nums[i], prev)
|
||||||
|
mmax_i = i if last > mmax else mmax_i
|
||||||
|
mmax = max(mmax, last)
|
||||||
|
|
||||||
|
return mmax_i, mmax
|
||||||
|
|
||||||
|
|
||||||
|
def sum_max_sub_array_accum(nums: list[int]) -> int:
|
||||||
|
accum: list[int] = [nums[0]]
|
||||||
|
|
||||||
|
for i in range(1, len(nums)):
|
||||||
|
prev: int = nums[i] + accum[-1]
|
||||||
|
accum.append(max(nums[i], prev))
|
||||||
|
|
||||||
|
return max(accum)
|
||||||
|
|
||||||
|
|
||||||
|
def accum_sub_array_maxes(nums: list[int]) -> list[int]:
|
||||||
|
accum: list[int] = [nums[0]]
|
||||||
|
|
||||||
|
for i in range(1, len(nums)):
|
||||||
|
prev: int = nums[i] + accum[-1]
|
||||||
|
accum.append(max(nums[i], prev))
|
||||||
|
|
||||||
|
return accum
|
||||||
|
|
||||||
|
|
||||||
|
def neighborly_node_from_list(inlist: list[list[int]]):
|
||||||
|
# Alias "Node" type for leetcode compat
|
||||||
|
Node = stdlib.NeighborlyNodeNicely
|
||||||
|
|
||||||
|
if len(inlist) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
outlist = [Node(i + 1, []) for i in range(len(inlist))]
|
||||||
|
|
||||||
|
for i in range(len(inlist)):
|
||||||
|
outlist[i].neighbors[:] = []
|
||||||
|
|
||||||
|
for neighbor_val in inlist[i]:
|
||||||
|
outlist[i].neighbors.append(outlist[neighbor_val - 1])
|
||||||
|
|
||||||
|
return outlist[0]
|
||||||
|
|
||||||
|
|
||||||
|
def neighborly_node_to_list(node) -> list[list[int]]:
|
||||||
|
serialized: dict[int, list[int]] = {}
|
||||||
|
|
||||||
|
for cur in traverse_neighborly_node(node, serialized):
|
||||||
|
if cur is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
serialized[cur.val] = [n.val for n in cur.neighbors]
|
||||||
|
|
||||||
|
return [v for _, v in sorted(serialized.items())]
|
||||||
|
|
||||||
|
|
||||||
|
def traverse_neighborly_node(
|
||||||
|
node: stdlib.NeighborlyNodeNicely, memo: collections.abc.Container[int]
|
||||||
|
) -> typing.Iterator[stdlib.NeighborlyNodeNicely | None]:
|
||||||
|
yield node
|
||||||
|
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for neighbor in node.neighbors:
|
||||||
|
if neighbor.val in memo:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield from traverse_neighborly_node(neighbor, memo)
|
||||||
|
|
||||||
|
|
||||||
|
def find_min_in_rotated_array(nums: list[int]) -> int:
|
||||||
|
if nums[0] <= nums[-1]:
|
||||||
|
return nums[0]
|
||||||
|
|
||||||
|
if len(nums) <= 3:
|
||||||
|
return min(nums)
|
||||||
|
|
||||||
|
if nums[len(nums) // 2] > nums[-1]:
|
||||||
|
return find_min_in_rotated_array(nums[len(nums) // 2 :])
|
||||||
|
|
||||||
|
return find_min_in_rotated_array(nums[: (len(nums) // 2) + 1])
|
15
leetcode/test_roman.py
Normal file
15
leetcode/test_roman.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
import roman
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("n", "expected"),
|
||||||
|
[
|
||||||
|
(3, "III"),
|
||||||
|
(58, "LVIII"),
|
||||||
|
(1994, "MCMXCIV"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_int_to_roman(n: int, expected: str):
|
||||||
|
assert roman.i2r(n) == expected
|
46
leetcode/test_spiral_matrix.py
Normal file
46
leetcode/test_spiral_matrix.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
import spiral_matrix
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("matrix", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[
|
||||||
|
["a", "b", "c"],
|
||||||
|
["d", "e", "f"],
|
||||||
|
["g", "h", "i"],
|
||||||
|
],
|
||||||
|
["a", "b", "c", "f", "i", "h", "g", "d", "e"],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[
|
||||||
|
[1, 2, 3, 4],
|
||||||
|
[5, 6, 7, 8],
|
||||||
|
[9, 10, 11, 12],
|
||||||
|
],
|
||||||
|
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||||
|
],
|
||||||
|
[] # noqa
|
||||||
|
+ [1, 2, 3, 4, 5, 6, 7, 8, 9] # right
|
||||||
|
+ [9, 9, 9] # down
|
||||||
|
+ [9, 8, 7, 6, 5, 4, 3, 2, 1] # left
|
||||||
|
+ [1, 1] # up
|
||||||
|
+ [1, 2, 3, 4, 5, 6, 7, 8] # right
|
||||||
|
+ [8] # down
|
||||||
|
+ [8, 7, 6, 5, 4, 3, 2] # left
|
||||||
|
+ [2, 3, 4, 5, 6, 7], # right
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_matrix_spiral(matrix, expected):
|
||||||
|
assert spiral_matrix.matrix_spiral(matrix) == expected
|
505
leetcode/test_stuff.py
Normal file
505
leetcode/test_stuff.py
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import stuff
|
||||||
|
import stdlib
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("n", "expected"),
|
||||||
|
[
|
||||||
|
(0, 0),
|
||||||
|
(1, 1),
|
||||||
|
(5, 2),
|
||||||
|
(4, 2),
|
||||||
|
(8, 2),
|
||||||
|
(9, 3),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_find_sqrt_ish(n: int, expected: int):
|
||||||
|
assert stuff.find_sqrt_ish(n) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("ops", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
[
|
||||||
|
("new",),
|
||||||
|
("push", -2),
|
||||||
|
("push", 0),
|
||||||
|
("push", -3),
|
||||||
|
("getMin",),
|
||||||
|
("pop",),
|
||||||
|
("top",),
|
||||||
|
("getMin",),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
-3,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
-2,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_min_stack(ops: list[tuple[str] | tuple[str, int]], expected: list[int | None]):
|
||||||
|
returned: list[int | None] = []
|
||||||
|
inst: stuff.MinStack | None = None
|
||||||
|
|
||||||
|
for op in ops:
|
||||||
|
if len(op) == 1:
|
||||||
|
if op[0] == "new":
|
||||||
|
inst = stuff.MinStack()
|
||||||
|
returned.append(None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
returned.append(getattr(inst, op[0])())
|
||||||
|
continue
|
||||||
|
|
||||||
|
method, arg = op
|
||||||
|
returned.append(getattr(inst, method)(arg))
|
||||||
|
|
||||||
|
assert returned == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("head", "expected"),
|
||||||
|
[
|
||||||
|
(None, None),
|
||||||
|
(
|
||||||
|
stdlib.ListNode(
|
||||||
|
4, stdlib.ListNode(2, stdlib.ListNode(1, stdlib.ListNode(3)))
|
||||||
|
),
|
||||||
|
stdlib.ListNode(
|
||||||
|
1, stdlib.ListNode(2, stdlib.ListNode(3, stdlib.ListNode(4)))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
stdlib.ListNode(
|
||||||
|
4,
|
||||||
|
stdlib.ListNode(
|
||||||
|
19,
|
||||||
|
stdlib.ListNode(
|
||||||
|
14,
|
||||||
|
stdlib.ListNode(
|
||||||
|
5,
|
||||||
|
stdlib.ListNode(
|
||||||
|
-3,
|
||||||
|
stdlib.ListNode(
|
||||||
|
1,
|
||||||
|
stdlib.ListNode(
|
||||||
|
8,
|
||||||
|
stdlib.ListNode(
|
||||||
|
5, stdlib.ListNode(11, stdlib.ListNode(15))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
stdlib.ListNode(
|
||||||
|
-3,
|
||||||
|
stdlib.ListNode(
|
||||||
|
1,
|
||||||
|
stdlib.ListNode(
|
||||||
|
4,
|
||||||
|
stdlib.ListNode(
|
||||||
|
5,
|
||||||
|
stdlib.ListNode(
|
||||||
|
5,
|
||||||
|
stdlib.ListNode(
|
||||||
|
8,
|
||||||
|
stdlib.ListNode(
|
||||||
|
11,
|
||||||
|
stdlib.ListNode(
|
||||||
|
14, stdlib.ListNode(15, stdlib.ListNode(19))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_sort_linked_list(
|
||||||
|
head: stdlib.LinkedListNode | None, expected: stdlib.LinkedListNode | None
|
||||||
|
):
|
||||||
|
if head is None:
|
||||||
|
assert stuff.sort_linked_list(head) == expected
|
||||||
|
return
|
||||||
|
|
||||||
|
assert stuff.linked_list_to_list(
|
||||||
|
stuff.sort_linked_list(head)
|
||||||
|
) == stuff.linked_list_to_list(expected)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("root", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
stdlib.Node(
|
||||||
|
1,
|
||||||
|
left=stdlib.Node(2, left=stdlib.Node(4), right=stdlib.Node(5)),
|
||||||
|
right=stdlib.Node(3, right=stdlib.Node(7)),
|
||||||
|
),
|
||||||
|
[1, None, 2, 3, None, 4, 5, 7, None],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_connect_binary_tree_right(
|
||||||
|
root: stdlib.ConnectableBinaryTreeNode | None, expected: list[int | None] | None
|
||||||
|
):
|
||||||
|
if expected is None:
|
||||||
|
assert root is None
|
||||||
|
return
|
||||||
|
|
||||||
|
connected, serialized = stuff.connect_binary_tree_right(root)
|
||||||
|
assert connected is not None
|
||||||
|
assert serialized == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("root", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
stdlib.Node(
|
||||||
|
4,
|
||||||
|
right=stdlib.Node(0),
|
||||||
|
left=stdlib.Node(9, right=stdlib.Node(1), left=stdlib.Node(5)),
|
||||||
|
),
|
||||||
|
1026,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_connect_binary_tree_sum_numbers(
|
||||||
|
root: stdlib.BinaryTreeNode | None, expected: int
|
||||||
|
):
|
||||||
|
assert stuff.sum_binary_tree_path_ints(root) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("inlist", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[3, 5, 1, 6, 2, 0, 8, None, None, 7, 4],
|
||||||
|
stdlib.TreeNode(
|
||||||
|
3,
|
||||||
|
left=stdlib.TreeNode(
|
||||||
|
5,
|
||||||
|
left=stdlib.TreeNode(6),
|
||||||
|
right=stdlib.TreeNode(
|
||||||
|
2,
|
||||||
|
left=stdlib.TreeNode(7),
|
||||||
|
right=stdlib.TreeNode(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
right=stdlib.TreeNode(
|
||||||
|
1,
|
||||||
|
left=stdlib.TreeNode(0),
|
||||||
|
right=stdlib.TreeNode(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_binary_tree_from_list(
|
||||||
|
inlist: list[int | None], expected: stdlib.BinaryTreeNode | None
|
||||||
|
):
|
||||||
|
assert stuff.binary_tree_from_list(inlist) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("preorder", "inorder", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[3, 9, 20, 15, 7],
|
||||||
|
[9, 3, 15, 20, 7],
|
||||||
|
stdlib.TreeNode(
|
||||||
|
3,
|
||||||
|
left=stdlib.TreeNode(9),
|
||||||
|
right=stdlib.TreeNode(
|
||||||
|
20, left=stdlib.TreeNode(15), right=stdlib.TreeNode(7)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[-1],
|
||||||
|
[-1],
|
||||||
|
stdlib.TreeNode(-1),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1, 2],
|
||||||
|
[1, 2],
|
||||||
|
stdlib.TreeNode(1, right=stdlib.TreeNode(2)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_binary_tree_from_preorder_inorder(
|
||||||
|
preorder: list[int], inorder: list[int], expected: stdlib.BinaryTreeNode
|
||||||
|
):
|
||||||
|
assert stuff.binary_tree_from_preorder_inorder(preorder, inorder) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("board", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[2, 3, 1, 1, 4],
|
||||||
|
[
|
||||||
|
[0, 1, 2, 3, 4],
|
||||||
|
[0, 1, 3, 4],
|
||||||
|
[0, 1, 4],
|
||||||
|
[0, 2, 3, 4],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_collect_complete_jump_paths_from_board(
|
||||||
|
board: list[int], expected: list[list[int]]
|
||||||
|
):
|
||||||
|
assert list(sorted(stuff.collect_complete_jump_paths_from_board(board))) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("board", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[2, 3, 1, 1, 4],
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[2, 3, 0, 1, 4],
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1, 2],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[6, 2, 6, 1, 7, 9, 3, 5, 3, 7, 2, 8, 9, 4, 7, 7, 2, 2, 8, 4, 6, 6, 1, 3],
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[3, 4, 3, 2, 5, 4, 3],
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[3, 2, 1],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1, 2, 3],
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_count_min_jumps_from_board(board: list[int], expected: int):
|
||||||
|
assert stuff.count_min_jumps_from_board(board) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("citations", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[3, 0, 6, 1, 5],
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1, 3, 1],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[100],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_h_index(citations: list[int], expected: int):
|
||||||
|
assert stuff.h_index(citations) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("cls",),
|
||||||
|
[
|
||||||
|
(stuff.SlowRandomizedSet,),
|
||||||
|
(stuff.RandomizedSet,),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_randomized_set(cls: type[stuff.RandomizedSet] | type[stuff.SlowRandomizedSet]):
|
||||||
|
inst = cls()
|
||||||
|
|
||||||
|
assert inst.insert(1) is True
|
||||||
|
assert inst.remove(2) is False
|
||||||
|
assert inst.insert(2) is True
|
||||||
|
assert inst.getRandom() in (1, 2)
|
||||||
|
assert inst.remove(1) is True
|
||||||
|
assert inst.insert(2) is False
|
||||||
|
assert inst.getRandom() == 2
|
||||||
|
|
||||||
|
inst = cls()
|
||||||
|
|
||||||
|
assert inst.insert(1) is True
|
||||||
|
assert inst.insert(10) is True
|
||||||
|
assert inst.insert(20) is True
|
||||||
|
assert inst.insert(30) is True
|
||||||
|
|
||||||
|
seen: set[int] = set()
|
||||||
|
|
||||||
|
for _ in range(10_000):
|
||||||
|
seen.add(inst.getRandom())
|
||||||
|
|
||||||
|
assert seen == {1, 10, 20, 30}
|
||||||
|
|
||||||
|
# ["remove","remove","insert","getRandom","remove","insert"]
|
||||||
|
# [[0],[0],[0],[],[0],[0]]
|
||||||
|
|
||||||
|
inst = cls()
|
||||||
|
|
||||||
|
assert inst.remove(0) is False
|
||||||
|
assert inst.remove(0) is False
|
||||||
|
assert inst.insert(0) is True
|
||||||
|
assert inst.getRandom() == 0
|
||||||
|
assert inst.remove(0) is True
|
||||||
|
assert inst.insert(0) is True
|
||||||
|
|
||||||
|
# ["RandomizedSet","insert","insert","remove","insert","remove","getRandom"]
|
||||||
|
# [[],[0],[1],[0],[2],[1],[]]
|
||||||
|
|
||||||
|
inst = cls()
|
||||||
|
|
||||||
|
assert inst.insert(0) is True
|
||||||
|
assert inst.insert(1) is True
|
||||||
|
assert inst.remove(0) is True
|
||||||
|
assert inst.insert(2) is True
|
||||||
|
assert inst.remove(1) is True
|
||||||
|
assert inst.getRandom() == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_trie_single_letter():
|
||||||
|
trie = stuff.Trie()
|
||||||
|
|
||||||
|
assert trie.insert("a") is None
|
||||||
|
assert trie.search("a") is True
|
||||||
|
assert trie.startsWith("a") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_trie_prefix_leaf():
|
||||||
|
trie = stuff.Trie()
|
||||||
|
|
||||||
|
assert trie.insert("apple") is None
|
||||||
|
assert trie.search("apple") is True
|
||||||
|
assert trie.search("app") is False
|
||||||
|
assert trie.startsWith("app") is True
|
||||||
|
assert trie.insert("app") is None
|
||||||
|
assert trie.search("app") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_trie_two_letter():
|
||||||
|
trie = stuff.Trie()
|
||||||
|
|
||||||
|
assert trie.insert("ab") is None
|
||||||
|
assert trie.search("a") is False
|
||||||
|
assert trie.startsWith("a") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_trie_busy():
|
||||||
|
trie = stuff.Trie()
|
||||||
|
|
||||||
|
assert trie.insert("app") is None
|
||||||
|
assert trie.insert("apple") is None
|
||||||
|
assert trie.insert("beer") is None
|
||||||
|
assert trie.insert("add") is None
|
||||||
|
assert trie.insert("jam") is None
|
||||||
|
assert trie.insert("rental") is None
|
||||||
|
assert trie.search("apps") is False
|
||||||
|
assert trie.search("app") is True
|
||||||
|
assert trie.search("ad") is False
|
||||||
|
assert trie.search("applepie") is False
|
||||||
|
assert trie.search("rest") is False
|
||||||
|
assert trie.search("jan") is False
|
||||||
|
assert trie.search("rent") is False
|
||||||
|
assert trie.search("beer") is True
|
||||||
|
assert trie.search("jam") is True
|
||||||
|
assert trie.startsWith("apps") is False
|
||||||
|
assert trie.startsWith("app") is True
|
||||||
|
assert trie.startsWith("ad") is True
|
||||||
|
assert trie.startsWith("applepie") is False
|
||||||
|
assert trie.startsWith("rest") is False
|
||||||
|
assert trie.startsWith("jan") is False
|
||||||
|
assert trie.startsWith("rent") is True
|
||||||
|
assert trie.startsWith("beer") is True
|
||||||
|
assert trie.startsWith("jam") is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("nums", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[-2, 1, -3, 4, -1, 2, 1, -5, 4],
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[1],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[5, 4, -1, 7, 8],
|
||||||
|
23,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
[-2, 1],
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
json.load(open(".testdata/max_sub_array0.json")),
|
||||||
|
11081,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_max_sub_array(nums: list[int], expected: int):
|
||||||
|
assert stuff.sum_max_sub_array(nums) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("inlist",),
|
||||||
|
[
|
||||||
|
([[2, 3, 4], [1, 7], [1], [1, 5, 6, 8], [4], [4], [2], [4]],),
|
||||||
|
([[2, 4], [1, 3], [2, 4], [1, 3]],),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_copy_neighborly_node(inlist):
|
||||||
|
orig = stuff.neighborly_node_from_list(inlist)
|
||||||
|
copied = stuff.neighborly_node_from_list(stuff.neighborly_node_to_list(orig))
|
||||||
|
|
||||||
|
assert id(orig) != id(copied)
|
||||||
|
assert orig == copied
|
||||||
|
assert stuff.neighborly_node_to_list(orig) == stuff.neighborly_node_to_list(copied)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("nums", "expected"),
|
||||||
|
[
|
||||||
|
([3, 4, 5, 1, 2], 1),
|
||||||
|
([4, 5, 6, 7, 0, 1, 2], 0),
|
||||||
|
([11, 13, 15, 17], 11),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_find_min_in_rotated_array(nums: list[int], expected: int):
|
||||||
|
assert stuff.find_min_in_rotated_array(nums) == expected
|
1
modernc/01/.gitignore
vendored
Normal file
1
modernc/01/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
getting-started
|
23
modernc/01/bad.c
Normal file
23
modernc/01/bad.c
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* This may look like nonsense, but really is -*- mode: C -*- */
|
||||||
|
|
||||||
|
/* The main thing that this program does. */
|
||||||
|
void main() {
|
||||||
|
// Decralations
|
||||||
|
int i;
|
||||||
|
double A[5] = {
|
||||||
|
9.0,
|
||||||
|
2.9,
|
||||||
|
3.E+25,
|
||||||
|
.00007,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Doing some work
|
||||||
|
for (i = 0; i < 5; ++i) {
|
||||||
|
printf("element %d is %g, \tits square is %g\n",
|
||||||
|
i,
|
||||||
|
A[i],
|
||||||
|
A[i]*A[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
24
modernc/01/getting-started.c
Normal file
24
modernc/01/getting-started.c
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* This may look like nonsense, but really is -*- mode: C -*- */
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* The main thing that this program does. */
|
||||||
|
int main(void) {
|
||||||
|
// Decralations
|
||||||
|
double A[5] = {
|
||||||
|
[0] = 9.0,
|
||||||
|
[1] = 2.9,
|
||||||
|
[4] = 3.E+25,
|
||||||
|
[3] = .00007,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Doing some work
|
||||||
|
for (size_t i = 0; i < 5; ++i) {
|
||||||
|
printf("element %zu is %g, \tits square is %g\n",
|
||||||
|
i,
|
||||||
|
A[i],
|
||||||
|
A[i]*A[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
2
modernc/03/.gitignore
vendored
Normal file
2
modernc/03/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
challenge-1
|
||||||
|
heron
|
90
modernc/03/array.c
Normal file
90
modernc/03/array.c
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include "array.h"
|
||||||
|
|
||||||
|
void IntArray_new(IntArray* a, size_t size) {
|
||||||
|
a->arr = malloc(size * sizeof(int));
|
||||||
|
a->used = 0;
|
||||||
|
a->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IntArray_append(IntArray* a, int el) {
|
||||||
|
if (a->used == a->size) {
|
||||||
|
a->size *= 2;
|
||||||
|
a->arr = realloc(a->arr, a->size * sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
a->used++;
|
||||||
|
|
||||||
|
a->arr[a->used] = el;
|
||||||
|
return a->used;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IntArray_get(IntArray* a, int idx, int dflt) {
|
||||||
|
if (idx > a->used) {
|
||||||
|
return dflt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a->arr[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
int IntArray_length(IntArray* a) {
|
||||||
|
return a->used;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntArray_mergesort(IntArray* a) {
|
||||||
|
IntArray left_side;
|
||||||
|
IntArray right_side;
|
||||||
|
|
||||||
|
size_t split = a->used / 2;
|
||||||
|
|
||||||
|
IntArray_new(&left_side, split);
|
||||||
|
IntArray_new(&right_side, split);
|
||||||
|
|
||||||
|
for (int i = 0; i < split; ++i) {
|
||||||
|
IntArray_append(&left_side, IntArray_get(a, i, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = split; i < a->used; ++i) {
|
||||||
|
IntArray_append(&right_side, IntArray_get(a, i, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
IntArray_mergesort(&left_side);
|
||||||
|
IntArray_mergesort(&right_side);
|
||||||
|
|
||||||
|
IntArray_mergesort_merge(a, &left_side, &right_side);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntArray_mergesort_merge(IntArray* a, IntArray* left_side, IntArray* right_side) {
|
||||||
|
IntArray_free(a);
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
size_t j = 0;
|
||||||
|
|
||||||
|
while (i < left_side->used && j < right_side->used) {
|
||||||
|
if (left_side->arr[i] <= right_side->arr[j]) {
|
||||||
|
IntArray_append(a, left_side->arr[i]);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
IntArray_append(a, right_side->arr[j]);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t li = 0; li < left_side->used; ++li) {
|
||||||
|
IntArray_append(a, left_side->arr[li]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t ri = 0; ri < right_side->used; ++ri) {
|
||||||
|
IntArray_append(a, right_side->arr[ri]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntArray_quicksort(IntArray* a) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntArray_free(IntArray* a) {
|
||||||
|
free(a->arr);
|
||||||
|
a->arr = NULL;
|
||||||
|
a->used = a->size = 0;
|
||||||
|
}
|
22
modernc/03/array.h
Normal file
22
modernc/03/array.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef INCLUDED_ARRAY_H
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int *arr;
|
||||||
|
size_t used;
|
||||||
|
size_t size;
|
||||||
|
} IntArray;
|
||||||
|
|
||||||
|
void IntArray_new(IntArray*, size_t);
|
||||||
|
|
||||||
|
int IntArray_append(IntArray*, int);
|
||||||
|
int IntArray_get(IntArray*, int, int);
|
||||||
|
int IntArray_length(IntArray*);
|
||||||
|
void IntArray_mergesort(IntArray*);
|
||||||
|
void IntArray_mergesort_merge(IntArray*, IntArray*, IntArray*);
|
||||||
|
void IntArray_quicksort(IntArray*);
|
||||||
|
|
||||||
|
void IntArray_free(IntArray*);
|
||||||
|
|
||||||
|
#define INCLUDED_ARRAY_H 1
|
||||||
|
#endif
|
114
modernc/03/challenge-1.c
Normal file
114
modernc/03/challenge-1.c
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "array.h"
|
||||||
|
|
||||||
|
#define IODASH "-"
|
||||||
|
|
||||||
|
void usage(char* prog) {
|
||||||
|
fprintf(stderr, "Usage: %s [-f|--input-file <input-file>] [-o|--output-file <output-file>]\n", prog);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char* input_file = IODASH;
|
||||||
|
char* output_file = IODASH;
|
||||||
|
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"input-file", required_argument, 0, 'f'},
|
||||||
|
{"output-file", required_argument, 0, 'o'},
|
||||||
|
};
|
||||||
|
|
||||||
|
int option_index = 0;
|
||||||
|
|
||||||
|
c = getopt_long(argc, argv, "f:o:", long_options, &option_index);
|
||||||
|
|
||||||
|
if (c == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case 'f':
|
||||||
|
input_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
output_file = optarg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(input_file, IODASH) == 0) {
|
||||||
|
fprintf(stderr, "(reading from stdin)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(output_file, IODASH) == 0) {
|
||||||
|
fprintf(stderr, "(writing to stdout)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("input_file='%s' output_file='%s'\n", input_file, output_file);
|
||||||
|
|
||||||
|
FILE* instream;
|
||||||
|
|
||||||
|
instream = stdin;
|
||||||
|
if (strcmp(input_file, IODASH) != 0) {
|
||||||
|
instream = fopen(input_file, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instream == NULL) {
|
||||||
|
perror("opening input file");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* outstream;
|
||||||
|
|
||||||
|
outstream = stdout;
|
||||||
|
if (strcmp(output_file, IODASH) != 0) {
|
||||||
|
outstream = fopen(output_file, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outstream == NULL) {
|
||||||
|
perror("opening output file");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int line_int;
|
||||||
|
ssize_t nread;
|
||||||
|
|
||||||
|
char* endptr = NULL;
|
||||||
|
char* line = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
IntArray accum;
|
||||||
|
IntArray_new(&accum, 100);
|
||||||
|
|
||||||
|
while ((nread = getline(&line, &len, instream)) != -1) {
|
||||||
|
IntArray_append(&accum, atoi(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Accumulated %i ints\n", IntArray_length(&accum));
|
||||||
|
|
||||||
|
IntArray_mergesort(&accum);
|
||||||
|
|
||||||
|
printf("Merge sorted:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < accum.used; ++i) {
|
||||||
|
printf("%i: %i\n", i, IntArray_get(&accum, i, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(input_file, IODASH) != 0) {
|
||||||
|
fclose(instream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(output_file, IODASH) != 0) {
|
||||||
|
fclose(outstream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
40
modernc/03/heron.c
Normal file
40
modernc/03/heron.c
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* lower and upper iteration limits centered around 1.0 */
|
||||||
|
static double const eps1m01 = 1.0 - 0x1P-01;
|
||||||
|
static double const eps1p01 = 1.0 + 0x1P-01;
|
||||||
|
static double const eps1m24 = 1.0 - 0x1P-24;
|
||||||
|
static double const eps1p24 = 1.0 + 0x1P-24;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[argc+1]) {
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
double const a = strtod(argv[i], 0);
|
||||||
|
double x = 1.0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
double prod = a*x;
|
||||||
|
if (prod < eps1m01) {
|
||||||
|
x *= 2.0;
|
||||||
|
} else if (eps1p01 < prod) {
|
||||||
|
x *= 0.5;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
double prod = a*x;
|
||||||
|
if ((prod < eps1m24) || (eps1p24 < prod)) {
|
||||||
|
x *= (2.0 - prod);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("heron: a=%.5e,\tx=%.5e,\ta*x=%.12f\n",
|
||||||
|
a, x, a*x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
1
modernc/05/.gitignore
vendored
Normal file
1
modernc/05/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
basic
|
9
modernc/05/basic.c
Normal file
9
modernc/05/basic.c
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
double x = 5.0;
|
||||||
|
double y = 3.0;
|
||||||
|
|
||||||
|
x = (x * 1.5) - y;
|
||||||
|
printf("x is \%g\n", x);
|
||||||
|
}
|
1
personal-home-page/.gitignore
vendored
Normal file
1
personal-home-page/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/vendor/
|
11
personal-home-page/blergh.php
Normal file
11
personal-home-page/blergh.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
$hello_cmd = new Commando\Command();
|
||||||
|
|
||||||
|
$hello_cmd->option()->require()->describedAs('A person\'s name');
|
||||||
|
|
||||||
|
$name = $hello_cmd[0];
|
||||||
|
|
||||||
|
echo "Hello, $name", PHP_EOL;
|
14
personal-home-page/composer.json
Normal file
14
personal-home-page/composer.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "meatballhat/personal-home-page",
|
||||||
|
"type": "library",
|
||||||
|
"require": {
|
||||||
|
"nategood/commando": "^0.4.0"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Dan Buch",
|
||||||
|
"email": "dan@meatballhat.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
124
personal-home-page/composer.lock
generated
Normal file
124
personal-home-page/composer.lock
generated
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "4aa26239cdb17b13a548c56ac84b5749",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "kevinlebrun/colors.php",
|
||||||
|
"version": "0.4.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/kevinlebrun/colors.php.git",
|
||||||
|
"reference": "d132f36d06e48ea080855af19b4bcb1fb615224a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/d132f36d06e48ea080855af19b4bcb1fb615224a",
|
||||||
|
"reference": "d132f36d06e48ea080855af19b4bcb1fb615224a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "3.7.*",
|
||||||
|
"satooshi/php-coveralls": "dev-master",
|
||||||
|
"squizlabs/php_codesniffer": "1.*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Colors": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kevin Le Brun",
|
||||||
|
"email": "lebrun.k@gmail.com",
|
||||||
|
"homepage": "http://kevinlebrun.fr",
|
||||||
|
"role": "developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Colors for PHP CLI scripts",
|
||||||
|
"homepage": "https://github.com/kevinlebrun/colors.php",
|
||||||
|
"keywords": [
|
||||||
|
"cli",
|
||||||
|
"color",
|
||||||
|
"colors",
|
||||||
|
"console",
|
||||||
|
"shell"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/kevinlebrun/colors.php/issues",
|
||||||
|
"source": "https://github.com/kevinlebrun/colors.php/tree/0.4.1"
|
||||||
|
},
|
||||||
|
"time": "2014-12-23T01:23:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nategood/commando",
|
||||||
|
"version": "0.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nategood/commando.git",
|
||||||
|
"reference": "8fedd49fcb694faf60d87d5c2c4defdffa298fa0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/nategood/commando/zipball/8fedd49fcb694faf60d87d5c2c4defdffa298fa0",
|
||||||
|
"reference": "8fedd49fcb694faf60d87d5c2c4defdffa298fa0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"kevinlebrun/colors.php": "~0.2",
|
||||||
|
"php": ">=5.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Commando": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nate Good",
|
||||||
|
"email": "me@nategood.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP CLI Commando Style",
|
||||||
|
"homepage": "http://github.com/nategood/commando",
|
||||||
|
"keywords": [
|
||||||
|
"automation",
|
||||||
|
"cli",
|
||||||
|
"command",
|
||||||
|
"command line",
|
||||||
|
"command line interface",
|
||||||
|
"scripting"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/nategood/commando/issues",
|
||||||
|
"source": "https://github.com/nategood/commando/tree/v0.4.0"
|
||||||
|
},
|
||||||
|
"time": "2019-07-10T02:37:11+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
11
personal-home-page/hello.php
Normal file
11
personal-home-page/hello.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php
|
||||||
|
echo "<h3>Hi, I'm a PHP script!</h3>";
|
||||||
|
?>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
personal-home-page/helloworld.php
Normal file
35
personal-home-page/helloworld.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$name = 'Flavio';
|
||||||
|
|
||||||
|
var_dump($name);
|
||||||
|
|
||||||
|
$age = 20;
|
||||||
|
|
||||||
|
var_dump($age);
|
||||||
|
|
||||||
|
$base = 20;
|
||||||
|
$height = 10;
|
||||||
|
|
||||||
|
$area = $base * $height;
|
||||||
|
|
||||||
|
$test = 'an example';
|
||||||
|
|
||||||
|
$example = "This is $test";
|
||||||
|
|
||||||
|
print $example . "\n";
|
||||||
|
|
||||||
|
$firstName = 'Flavio';
|
||||||
|
$lastName = 'Copes';
|
||||||
|
|
||||||
|
$fullName = $firstName . ' ' . $lastName;
|
||||||
|
|
||||||
|
print '$fullName = ' . $fullName . "\n";
|
||||||
|
|
||||||
|
print strlen($fullName) . "\n";
|
||||||
|
|
||||||
|
$weirdName = str_replace('avio', 'ower', $fullName);
|
||||||
|
|
||||||
|
print '$weirdName = ' . $weirdName . "\n";
|
||||||
|
|
||||||
|
print 'exploded $weirdName = ' . var_dump(explode(' ', $weirdName)) . "\n";
|
1
personal-home-page/phpinfo.php
Normal file
1
personal-home-page/phpinfo.php
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?php phpinfo(); ?>
|
6
personal-home-page/pong.php
Normal file
6
personal-home-page/pong.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<pre>
|
||||||
|
<?php
|
||||||
|
echo $_SERVER['HTTP_USER_AGENT'], PHP_EOL;
|
||||||
|
var_dump($_SERVER);
|
||||||
|
?>
|
||||||
|
</pre>
|
1876
piston-tutorials/getting-started/Cargo.lock
generated
Normal file
1876
piston-tutorials/getting-started/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
piston-tutorials/getting-started/Cargo.toml
Normal file
17
piston-tutorials/getting-started/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "spinning-square"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"Dan Buch <dan@meatballhat.com>",
|
||||||
|
"TyOverby <ty@pre-alpha.com>",
|
||||||
|
"Nikita Pekin <contact@nikitapek.in>"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "spinning-square"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
piston = "0.53.0"
|
||||||
|
piston2d-graphics = "0.43.0"
|
||||||
|
pistoncore-glutin_window = "0.70.1"
|
||||||
|
piston2d-opengl_graphics = "0.82.0"
|
70
piston-tutorials/getting-started/src/main.rs
Normal file
70
piston-tutorials/getting-started/src/main.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
extern crate glutin_window;
|
||||||
|
extern crate graphics;
|
||||||
|
extern crate opengl_graphics;
|
||||||
|
extern crate piston;
|
||||||
|
|
||||||
|
use glutin_window::GlutinWindow as Window;
|
||||||
|
use opengl_graphics::{GlGraphics, OpenGL};
|
||||||
|
use piston::event_loop::{EventSettings, Events};
|
||||||
|
use piston::input::{RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
|
||||||
|
use piston::window::WindowSettings;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
gl: GlGraphics,
|
||||||
|
rotation: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn render(&mut self, args: &RenderArgs) {
|
||||||
|
use graphics::*;
|
||||||
|
|
||||||
|
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
|
||||||
|
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
|
||||||
|
|
||||||
|
let square = rectangle::square(0.0, 0.0, 50.0);
|
||||||
|
let rotation = self.rotation;
|
||||||
|
let (x, y) = (args.window_size[0] / 2.0, args.window_size[1] / 2.0);
|
||||||
|
|
||||||
|
self.gl.draw(args.viewport(), |c, gl| {
|
||||||
|
clear(GREEN, gl);
|
||||||
|
|
||||||
|
let transform = c
|
||||||
|
.transform
|
||||||
|
.trans(x, y)
|
||||||
|
.rot_rad(rotation)
|
||||||
|
.trans(-25.0, -25.0);
|
||||||
|
|
||||||
|
rectangle(RED, square, transform, gl);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, args: &UpdateArgs) {
|
||||||
|
self.rotation += 2.0 * args.dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opengl = OpenGL::V3_2;
|
||||||
|
|
||||||
|
let mut window: Window = WindowSettings::new("spinning-square", [200, 200])
|
||||||
|
.graphics_api(opengl)
|
||||||
|
.exit_on_esc(true)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut app = App {
|
||||||
|
gl: GlGraphics::new(opengl),
|
||||||
|
rotation: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut events = Events::new(EventSettings::new());
|
||||||
|
while let Some(e) = events.next(&mut window) {
|
||||||
|
if let Some(args) = e.render_args() {
|
||||||
|
app.render(&args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(args) = e.update_args() {
|
||||||
|
app.update(&args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1876
piston-tutorials/sudoku/Cargo.lock
generated
Normal file
1876
piston-tutorials/sudoku/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
piston-tutorials/sudoku/Cargo.toml
Normal file
12
piston-tutorials/sudoku/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "sudoku"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
piston = "0.53.2"
|
||||||
|
piston2d-graphics = "0.43.0"
|
||||||
|
piston2d-opengl_graphics = "0.82.0"
|
||||||
|
pistoncore-glutin_window = "0.70.1"
|
BIN
piston-tutorials/sudoku/assets/FiraSans-Regular.ttf
Normal file
BIN
piston-tutorials/sudoku/assets/FiraSans-Regular.ttf
Normal file
Binary file not shown.
99
piston-tutorials/sudoku/assets/LICENSE
Normal file
99
piston-tutorials/sudoku/assets/LICENSE
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
|
||||||
|
with Reserved Font Name Fira Sans.
|
||||||
|
|
||||||
|
Copyright (c) 2014, Mozilla Foundation https://mozilla.org/
|
||||||
|
with Reserved Font Name Fira Mono.
|
||||||
|
|
||||||
|
Copyright (c) 2014, Telefonica S.A.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
155
piston-tutorials/sudoku/src/gameboard.rs
Normal file
155
piston-tutorials/sudoku/src/gameboard.rs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
//! Game board logic.
|
||||||
|
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
const SIZE: usize = 9;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
|
pub struct Cell {
|
||||||
|
pub value: u8,
|
||||||
|
pub loaded: bool,
|
||||||
|
pub invalid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Gameboard {
|
||||||
|
pub cells: [[Cell; SIZE]; SIZE],
|
||||||
|
pub completed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gameboard {
|
||||||
|
pub fn new() -> Gameboard {
|
||||||
|
Gameboard {
|
||||||
|
cells: [[Cell::default(); SIZE]; SIZE],
|
||||||
|
completed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_cells(cells: [[u8; SIZE]; SIZE]) -> Gameboard {
|
||||||
|
let mut ret = Gameboard::new();
|
||||||
|
for (i, row) in cells.iter().enumerate() {
|
||||||
|
for (j, &col) in row.iter().enumerate() {
|
||||||
|
ret.cells[i][j] = Cell {
|
||||||
|
value: col,
|
||||||
|
loaded: col != 0,
|
||||||
|
invalid: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn char(&self, ind: [usize; 2]) -> Option<char> {
|
||||||
|
Some(match self.cells[ind[1]][ind[0]].value {
|
||||||
|
1 => '1',
|
||||||
|
2 => '2',
|
||||||
|
3 => '3',
|
||||||
|
4 => '4',
|
||||||
|
5 => '5',
|
||||||
|
6 => '6',
|
||||||
|
7 => '7',
|
||||||
|
8 => '8',
|
||||||
|
9 => '9',
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, ind: [usize; 2], val: u8) {
|
||||||
|
if !self.cells[ind[1]][ind[0]].loaded {
|
||||||
|
self.validate(ind, val);
|
||||||
|
self.cells[ind[1]][ind[0]].value = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.completed = self
|
||||||
|
.cells
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.all(|cell| !cell.invalid && cell.value != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_sdm(filename: &str) -> Self {
|
||||||
|
let data = read_to_string(filename).expect("failed to read SDM file");
|
||||||
|
let mut cells = [[Cell::default(); SIZE]; SIZE];
|
||||||
|
let mut row = 0;
|
||||||
|
let mut col = 0;
|
||||||
|
for c in data.chars() {
|
||||||
|
if col == SIZE {
|
||||||
|
col = 0;
|
||||||
|
row += 1;
|
||||||
|
}
|
||||||
|
if let Some(v) = c.to_digit(10) {
|
||||||
|
let value = v as u8;
|
||||||
|
cells[row][col] = Cell {
|
||||||
|
value,
|
||||||
|
loaded: value != 0,
|
||||||
|
invalid: false,
|
||||||
|
};
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cells,
|
||||||
|
completed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&mut self, ind: [usize; 2], val: u8) {
|
||||||
|
let [b, a] = ind;
|
||||||
|
for i in 0..SIZE {
|
||||||
|
if i == a {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[a][i].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..SIZE {
|
||||||
|
if i == b {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[i][b].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (row, col) = (a / 3, b / 3);
|
||||||
|
for i in 3 * row..3 * row + 3 {
|
||||||
|
for j in 3 * col..3 * col + 3 {
|
||||||
|
if i == a && j == b {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.cells[i][j].value == val {
|
||||||
|
self.cells[a][b].invalid = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cells[a][b].invalid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn load_sdm() {
|
||||||
|
let got = Gameboard::load_sdm("static/puzzle.sdm");
|
||||||
|
let want = Gameboard::from_cells([
|
||||||
|
[0, 1, 6, 4, 0, 0, 0, 0, 0],
|
||||||
|
[2, 0, 0, 0, 0, 9, 0, 0, 0],
|
||||||
|
[4, 0, 0, 0, 0, 0, 0, 6, 2],
|
||||||
|
[0, 7, 0, 2, 3, 0, 1, 0, 0],
|
||||||
|
[1, 0, 0, 0, 0, 0, 0, 0, 3],
|
||||||
|
[0, 0, 3, 0, 8, 7, 0, 4, 0],
|
||||||
|
[9, 6, 0, 0, 0, 0, 0, 0, 5],
|
||||||
|
[0, 0, 0, 8, 0, 0, 0, 0, 7],
|
||||||
|
[0, 0, 0, 0, 0, 6, 8, 2, 0],
|
||||||
|
]);
|
||||||
|
assert_eq!(got, want);
|
||||||
|
}
|
||||||
|
}
|
57
piston-tutorials/sudoku/src/gameboard_controller.rs
Normal file
57
piston-tutorials/sudoku/src/gameboard_controller.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//! Gameboard controller
|
||||||
|
|
||||||
|
use piston::GenericEvent;
|
||||||
|
|
||||||
|
use crate::Gameboard;
|
||||||
|
|
||||||
|
pub struct GameboardController {
|
||||||
|
pub gameboard: Gameboard,
|
||||||
|
pub selected_cell: Option<[usize; 2]>,
|
||||||
|
cursor_pos: [f64; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardController {
|
||||||
|
pub fn new(gameboard: Gameboard) -> GameboardController {
|
||||||
|
GameboardController {
|
||||||
|
gameboard: gameboard,
|
||||||
|
selected_cell: None,
|
||||||
|
cursor_pos: [0.0; 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event<E: GenericEvent>(&mut self, pos: [f64; 2], size: f64, e: &E) {
|
||||||
|
use piston::input::{Button, Key, MouseButton};
|
||||||
|
|
||||||
|
if let Some(pos) = e.mouse_cursor_args() {
|
||||||
|
self.cursor_pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Button::Mouse(MouseButton::Left)) = e.press_args() {
|
||||||
|
let x = self.cursor_pos[0] - pos[0];
|
||||||
|
let y = self.cursor_pos[1] - pos[1];
|
||||||
|
|
||||||
|
if x >= 0.0 && x < size && y >= 0.0 && y < size {
|
||||||
|
let cell_x = (x / size * 9.0) as usize;
|
||||||
|
let cell_y = (y / size * 9.0) as usize;
|
||||||
|
self.selected_cell = Some([cell_x, cell_y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Button::Keyboard(key)) = e.press_args() {
|
||||||
|
if let Some(ind) = self.selected_cell {
|
||||||
|
match key {
|
||||||
|
Key::D1 => self.gameboard.set(ind, 1),
|
||||||
|
Key::D2 => self.gameboard.set(ind, 2),
|
||||||
|
Key::D3 => self.gameboard.set(ind, 3),
|
||||||
|
Key::D4 => self.gameboard.set(ind, 4),
|
||||||
|
Key::D5 => self.gameboard.set(ind, 5),
|
||||||
|
Key::D6 => self.gameboard.set(ind, 6),
|
||||||
|
Key::D7 => self.gameboard.set(ind, 7),
|
||||||
|
Key::D8 => self.gameboard.set(ind, 8),
|
||||||
|
Key::D9 => self.gameboard.set(ind, 9),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
210
piston-tutorials/sudoku/src/gameboard_view.rs
Normal file
210
piston-tutorials/sudoku/src/gameboard_view.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
//! Gameboard view.
|
||||||
|
|
||||||
|
use graphics::character::CharacterCache;
|
||||||
|
use graphics::types::Color;
|
||||||
|
use graphics::{Context, Graphics};
|
||||||
|
|
||||||
|
use crate::gameboard_controller::GameboardController;
|
||||||
|
|
||||||
|
pub struct GameboardViewSettings {
|
||||||
|
pub position: [f64; 2],
|
||||||
|
pub size: f64,
|
||||||
|
pub background_color: Color,
|
||||||
|
pub border_color: Color,
|
||||||
|
pub board_edge_color: Color,
|
||||||
|
pub section_edge_color: Color,
|
||||||
|
pub cell_edge_color: Color,
|
||||||
|
pub board_edge_radius: f64,
|
||||||
|
pub section_edge_radius: f64,
|
||||||
|
pub cell_edge_radius: f64,
|
||||||
|
pub selected_cell_background_color: Color,
|
||||||
|
pub text_color: Color,
|
||||||
|
pub loaded_cell_background_color: Color,
|
||||||
|
pub invalid_cell_background_color: Color,
|
||||||
|
pub invalid_selected_cell_background_color: Color,
|
||||||
|
pub completed_background_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardViewSettings {
|
||||||
|
pub fn new() -> GameboardViewSettings {
|
||||||
|
GameboardViewSettings {
|
||||||
|
position: [10.0; 2],
|
||||||
|
size: 400.0,
|
||||||
|
background_color: [0.8, 0.8, 1.0, 1.0],
|
||||||
|
border_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
board_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
section_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
cell_edge_color: [0.0, 0.0, 0.2, 1.0],
|
||||||
|
board_edge_radius: 3.0,
|
||||||
|
section_edge_radius: 2.0,
|
||||||
|
cell_edge_radius: 1.0,
|
||||||
|
selected_cell_background_color: [0.9, 0.9, 1.0, 1.0],
|
||||||
|
text_color: [0.0, 0.0, 0.1, 1.0],
|
||||||
|
loaded_cell_background_color: [1.0, 1.0, 1.0, 1.0],
|
||||||
|
invalid_cell_background_color: [1.0, 0.0, 0.0, 1.0],
|
||||||
|
invalid_selected_cell_background_color: [1.0, 0.0, 0.5, 1.0],
|
||||||
|
completed_background_color: [0.0, 1.0, 0.0, 1.0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GameboardView {
|
||||||
|
pub settings: GameboardViewSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameboardView {
|
||||||
|
pub fn new(settings: GameboardViewSettings) -> GameboardView {
|
||||||
|
GameboardView { settings: settings }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw<G: Graphics, C>(
|
||||||
|
&self,
|
||||||
|
controller: &GameboardController,
|
||||||
|
glyphs: &mut C,
|
||||||
|
c: &Context,
|
||||||
|
g: &mut G,
|
||||||
|
) where
|
||||||
|
C: CharacterCache<Texture = G::Texture>,
|
||||||
|
{
|
||||||
|
use graphics::{Image, Line, Rectangle, Transformed};
|
||||||
|
|
||||||
|
let ref settings = self.settings;
|
||||||
|
let board_rect = [
|
||||||
|
settings.position[0],
|
||||||
|
settings.position[1],
|
||||||
|
settings.size,
|
||||||
|
settings.size,
|
||||||
|
];
|
||||||
|
|
||||||
|
if controller.gameboard.completed {
|
||||||
|
Rectangle::new(settings.completed_background_color).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Rectangle::new(settings.background_color).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
for j in 0..9 {
|
||||||
|
if controller.gameboard.cells[i][j].loaded {
|
||||||
|
color_cell(
|
||||||
|
settings,
|
||||||
|
[j, i],
|
||||||
|
settings.loaded_cell_background_color,
|
||||||
|
c,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
} else if controller.gameboard.cells[i][j].invalid {
|
||||||
|
color_cell(
|
||||||
|
settings,
|
||||||
|
[j, i],
|
||||||
|
settings.invalid_cell_background_color,
|
||||||
|
c,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ind) = controller.selected_cell {
|
||||||
|
let cell = controller.gameboard.cells[ind[1]][ind[0]];
|
||||||
|
let color = if !cell.loaded {
|
||||||
|
if !cell.invalid {
|
||||||
|
settings.selected_cell_background_color
|
||||||
|
} else {
|
||||||
|
settings.invalid_selected_cell_background_color
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settings.loaded_cell_background_color
|
||||||
|
};
|
||||||
|
color_cell(settings, ind, color, c, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text_image = Image::new_color(settings.text_color);
|
||||||
|
let cell_size = settings.size / 9.0;
|
||||||
|
|
||||||
|
for j in 0..9 {
|
||||||
|
for i in 0..9 {
|
||||||
|
if let Some(ch) = controller.gameboard.char([i, j]) {
|
||||||
|
let pos = [
|
||||||
|
settings.position[0] + i as f64 * cell_size + 15.0,
|
||||||
|
settings.position[1] + j as f64 * cell_size + 34.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Ok(character) = glyphs.character(34, ch) {
|
||||||
|
let ch_x = pos[0] + character.left();
|
||||||
|
let ch_y = pos[1] - character.top();
|
||||||
|
let text_image = text_image.src_rect([
|
||||||
|
character.atlas_offset[0],
|
||||||
|
character.atlas_offset[1],
|
||||||
|
character.atlas_size[0],
|
||||||
|
character.atlas_size[1],
|
||||||
|
]);
|
||||||
|
text_image.draw(
|
||||||
|
character.texture,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform.trans(ch_x, ch_y),
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell_edge = Line::new(settings.cell_edge_color, settings.cell_edge_radius);
|
||||||
|
let section_edge = Line::new(settings.section_edge_color, settings.section_edge_radius);
|
||||||
|
|
||||||
|
for i in 0..9 {
|
||||||
|
let x = settings.position[0] + i as f64 / 9.0 * settings.size;
|
||||||
|
let y = settings.position[1] + i as f64 / 9.0 * settings.size;
|
||||||
|
let x2 = settings.position[0] + settings.size;
|
||||||
|
let y2 = settings.position[1] + settings.size;
|
||||||
|
|
||||||
|
let vline = [x, settings.position[1], x, y2];
|
||||||
|
let hline = [settings.position[0], y, x2, y];
|
||||||
|
|
||||||
|
if (i % 3) == 0 {
|
||||||
|
section_edge.draw(vline, &c.draw_state, c.transform, g);
|
||||||
|
section_edge.draw(hline, &c.draw_state, c.transform, g);
|
||||||
|
} else {
|
||||||
|
cell_edge.draw(vline, &c.draw_state, c.transform, g);
|
||||||
|
cell_edge.draw(hline, &c.draw_state, c.transform, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle::new_border(settings.board_edge_color, settings.board_edge_radius).draw(
|
||||||
|
board_rect,
|
||||||
|
&c.draw_state,
|
||||||
|
c.transform,
|
||||||
|
g,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_cell<G: Graphics>(
|
||||||
|
settings: &GameboardViewSettings,
|
||||||
|
ind: [usize; 2],
|
||||||
|
color: [f32; 4],
|
||||||
|
c: &Context,
|
||||||
|
g: &mut G,
|
||||||
|
) {
|
||||||
|
use graphics::Rectangle;
|
||||||
|
|
||||||
|
let cell_size = settings.size / 9.0;
|
||||||
|
let pos = [ind[0] as f64 * cell_size, ind[1] as f64 * cell_size];
|
||||||
|
let cell_rect = [
|
||||||
|
settings.position[0] + pos[0],
|
||||||
|
settings.position[1] + pos[1],
|
||||||
|
cell_size,
|
||||||
|
cell_size,
|
||||||
|
];
|
||||||
|
Rectangle::new(color).draw(cell_rect, &c.draw_state, c.transform, g);
|
||||||
|
}
|
57
piston-tutorials/sudoku/src/main.rs
Normal file
57
piston-tutorials/sudoku/src/main.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//////#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! An Sudoko please.
|
||||||
|
|
||||||
|
extern crate glutin_window;
|
||||||
|
|
||||||
|
use glutin_window::GlutinWindow;
|
||||||
|
use opengl_graphics::{Filter, GlGraphics, GlyphCache, OpenGL, TextureSettings};
|
||||||
|
use piston::event_loop::{EventSettings, Events};
|
||||||
|
use piston::{EventLoop, RenderEvent, WindowSettings};
|
||||||
|
|
||||||
|
pub use crate::gameboard::Gameboard;
|
||||||
|
pub use crate::gameboard_controller::GameboardController;
|
||||||
|
pub use crate::gameboard_view::{GameboardView, GameboardViewSettings};
|
||||||
|
|
||||||
|
mod gameboard;
|
||||||
|
mod gameboard_controller;
|
||||||
|
mod gameboard_view;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let opengl = OpenGL::V3_2;
|
||||||
|
let settings = WindowSettings::new("Sudoku", (640, 480))
|
||||||
|
.exit_on_esc(true)
|
||||||
|
.graphics_api(opengl)
|
||||||
|
.vsync(true);
|
||||||
|
let mut window: GlutinWindow = settings.build().expect("could not create window");
|
||||||
|
let mut events = Events::new(EventSettings::new().lazy(true));
|
||||||
|
let mut gl = GlGraphics::new(opengl);
|
||||||
|
|
||||||
|
let args: Vec<_> = std::env::args().collect();
|
||||||
|
let infile = args.get(1).expect("usage: sudoku <sdm-file>");
|
||||||
|
|
||||||
|
let gameboard = Gameboard::load_sdm(infile);
|
||||||
|
let mut gameboard_controller = GameboardController::new(gameboard);
|
||||||
|
let gameboard_view_settings = GameboardViewSettings::new();
|
||||||
|
let gameboard_view = GameboardView::new(gameboard_view_settings);
|
||||||
|
|
||||||
|
let texture_settings = TextureSettings::new().filter(Filter::Nearest);
|
||||||
|
let ref mut glyphs = GlyphCache::new("assets/FiraSans-Regular.ttf", (), texture_settings)
|
||||||
|
.expect("Could not load font");
|
||||||
|
|
||||||
|
while let Some(e) = events.next(&mut window) {
|
||||||
|
gameboard_controller.event(
|
||||||
|
gameboard_view.settings.position,
|
||||||
|
gameboard_view.settings.size,
|
||||||
|
&e,
|
||||||
|
);
|
||||||
|
if let Some(args) = e.render_args() {
|
||||||
|
gl.draw(args.viewport(), |c, g| {
|
||||||
|
use graphics::clear;
|
||||||
|
|
||||||
|
clear([1.0; 4], g);
|
||||||
|
gameboard_view.draw(&gameboard_controller, glyphs, &c, g);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
piston-tutorials/sudoku/static/puzzle-almost-solved.sdm
Normal file
1
piston-tutorials/sudoku/static/puzzle-almost-solved.sdm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
517962483236847915498351762371695248654218397829734156765129834142583679983476520
|
1
piston-tutorials/sudoku/static/puzzle.sdm
Normal file
1
piston-tutorials/sudoku/static/puzzle.sdm
Normal file
@@ -0,0 +1 @@
|
|||||||
|
016400000200009000400000062070230100100000003003087040960000005000800007000006820
|
14
rustbyexample/.gitignore
vendored
14
rustbyexample/.gitignore
vendored
@@ -1,11 +1,3 @@
|
|||||||
/custom_types/*
|
/out/
|
||||||
/hello/*
|
/*.d/out
|
||||||
/primitives/*
|
/*.d/**/out
|
||||||
/variable_bindings/*
|
|
||||||
/types/*
|
|
||||||
|
|
||||||
!/custom_types/*.rs
|
|
||||||
!/hello/*.rs
|
|
||||||
!/primitives/*.rs
|
|
||||||
!/variable_bindings/*.rs
|
|
||||||
!/types/*.rs
|
|
||||||
|
18
rustbyexample/conversion.d/from_into.rs
Normal file
18
rustbyexample/conversion.d/from_into.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use std::convert::From;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Number {
|
||||||
|
value: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i32> for Number {
|
||||||
|
fn from(item: i32) -> Self {
|
||||||
|
Number { value: item }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let num = Number::from(30);
|
||||||
|
println!("My number is {:?}", num);
|
||||||
|
}
|
19
rustbyexample/conversion.d/from_into2.rs
Normal file
19
rustbyexample/conversion.d/from_into2.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use std::convert::Into;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Number {
|
||||||
|
value: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Number> for i32 {
|
||||||
|
fn into(self) -> Number {
|
||||||
|
Number { value: self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let int = 5;
|
||||||
|
let num: Number = int.into();
|
||||||
|
println!("My number is {:?}", num);
|
||||||
|
}
|
16
rustbyexample/conversion.d/string.rs
Normal file
16
rustbyexample/conversion.d/string.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
struct Circle {
|
||||||
|
radius: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Circle {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Circle of radius {}", self.radius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let circle = Circle { radius: 6 };
|
||||||
|
println!("{}", circle.to_string());
|
||||||
|
}
|
7
rustbyexample/conversion.d/string2.rs
Normal file
7
rustbyexample/conversion.d/string2.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fn main() {
|
||||||
|
let parsed: i32 = "5".parse().unwrap();
|
||||||
|
let turbo_parsed = "10".parse::<i32>().unwrap();
|
||||||
|
|
||||||
|
let sum = parsed + turbo_parsed;
|
||||||
|
println!("Sum: {:?}", sum);
|
||||||
|
}
|
28
rustbyexample/conversion.d/try_from_try_into.rs
Normal file
28
rustbyexample/conversion.d/try_from_try_into.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct EvenNumber(i32);
|
||||||
|
|
||||||
|
impl TryFrom<i32> for EvenNumber {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||||
|
if value % 2 == 0 {
|
||||||
|
Ok(EvenNumber(value))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
|
||||||
|
assert_eq!(EvenNumber::try_from(5), Err(()));
|
||||||
|
|
||||||
|
let result: Result<EvenNumber, ()> = 8i32.try_into();
|
||||||
|
assert_eq!(result, Ok(EvenNumber(8)));
|
||||||
|
|
||||||
|
let result: Result<EvenNumber, ()> = 5i32.try_into();
|
||||||
|
assert_eq!(result, Err(()));
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Person {
|
struct Person {
|
||||||
name: String,
|
name: String,
|
25
rustbyexample/expression.rs
Normal file
25
rustbyexample/expression.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#[allow(path_statements)]
|
||||||
|
#[allow(unused_must_use)]
|
||||||
|
fn main() {
|
||||||
|
let x = 5;
|
||||||
|
x;
|
||||||
|
x + 1;
|
||||||
|
15;
|
||||||
|
|
||||||
|
let x = 5u32;
|
||||||
|
|
||||||
|
let y = {
|
||||||
|
let x_squared = x * x;
|
||||||
|
let x_cube = x_squared * x;
|
||||||
|
|
||||||
|
x_cube + x_squared + x
|
||||||
|
};
|
||||||
|
|
||||||
|
let z = {
|
||||||
|
2 * x;
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("x is {:?}", x);
|
||||||
|
println!("y is {:?}", y);
|
||||||
|
println!("z is {:?}", z);
|
||||||
|
}
|
13
rustbyexample/flow_control.d/for.rs
Normal file
13
rustbyexample/flow_control.d/for.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
fn main() {
|
||||||
|
for n in 1..101 {
|
||||||
|
if n % 15 == 0 {
|
||||||
|
println!("fizzbuzz");
|
||||||
|
} else if n % 3 == 0 {
|
||||||
|
println!("fizz");
|
||||||
|
} else if n % 5 == 0 {
|
||||||
|
println!("buzz");
|
||||||
|
} else {
|
||||||
|
println!("{}", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
rustbyexample/flow_control.d/for2.rs
Normal file
13
rustbyexample/flow_control.d/for2.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
fn main() {
|
||||||
|
for n in 1..=100 {
|
||||||
|
if n % 15 == 0 {
|
||||||
|
println!("fizzbuzz");
|
||||||
|
} else if n % 3 == 0 {
|
||||||
|
println!("fizz");
|
||||||
|
} else if n % 5 == 0 {
|
||||||
|
println!("buzz");
|
||||||
|
} else {
|
||||||
|
println!("{}", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
rustbyexample/flow_control.d/for3.rs
Normal file
12
rustbyexample/flow_control.d/for3.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
fn main() {
|
||||||
|
let names = vec!["Bob", "Frank", "Ferris"];
|
||||||
|
|
||||||
|
for name in names.iter() {
|
||||||
|
match name {
|
||||||
|
&"Ferris" => println!("There is a rustacean among us!"),
|
||||||
|
_ => println!("Hello {}", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("names: {:?}", names);
|
||||||
|
}
|
12
rustbyexample/flow_control.d/for4.rs
Normal file
12
rustbyexample/flow_control.d/for4.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
fn main() {
|
||||||
|
let names = vec!["Bob", "Frank", "Ferris"];
|
||||||
|
|
||||||
|
for name in names.into_iter() {
|
||||||
|
match name {
|
||||||
|
"Ferris" => println!("There is a rustacean among us!"),
|
||||||
|
_ => println!("Hello {}", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//println!("names: {:?}", names);
|
||||||
|
}
|
12
rustbyexample/flow_control.d/for5.rs
Normal file
12
rustbyexample/flow_control.d/for5.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
fn main() {
|
||||||
|
let mut names = vec!["Bob", "Frank", "Ferris"];
|
||||||
|
|
||||||
|
for name in names.iter_mut() {
|
||||||
|
*name = match name {
|
||||||
|
&mut "Ferris" => "There is a rustacean among us!",
|
||||||
|
_ => "Hello",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("names: {:?}", names);
|
||||||
|
}
|
23
rustbyexample/flow_control.d/if_else.rs
Normal file
23
rustbyexample/flow_control.d/if_else.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
fn main() {
|
||||||
|
let n = 5;
|
||||||
|
|
||||||
|
if n < 0 {
|
||||||
|
print!("{} is negative", n);
|
||||||
|
} else if n > 0 {
|
||||||
|
print!("{} is positive", n);
|
||||||
|
} else {
|
||||||
|
print!("{} is zero", n);
|
||||||
|
}
|
||||||
|
|
||||||
|
let big_n = if n < 10 && n > -10 {
|
||||||
|
println!(", and is a small number, increase net-fold");
|
||||||
|
|
||||||
|
10 * n
|
||||||
|
} else {
|
||||||
|
println!(", and is a big number, halve the number");
|
||||||
|
|
||||||
|
n / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{} -> {}", n, big_n);
|
||||||
|
}
|
25
rustbyexample/flow_control.d/if_let.rs
Normal file
25
rustbyexample/flow_control.d/if_let.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
fn main() {
|
||||||
|
let number = Some(7);
|
||||||
|
let letter: Option<i32> = None;
|
||||||
|
let emoticon: Option<i32> = None;
|
||||||
|
|
||||||
|
if let Some(i) = number {
|
||||||
|
println!("Matched {:?}!", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(i) = letter {
|
||||||
|
println!("Matched {:?}!", i);
|
||||||
|
} else {
|
||||||
|
println!("Didn't match a number. Let's go with a letter!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let i_like_letters = false;
|
||||||
|
|
||||||
|
if let Some(i) = emoticon {
|
||||||
|
println!("Matched {:?}!", i);
|
||||||
|
} else if i_like_letters {
|
||||||
|
println!("Didn't match a number. Let's go with a letter!");
|
||||||
|
} else {
|
||||||
|
println!("I don't like letters. Let's go with an emoticon :)!");
|
||||||
|
}
|
||||||
|
}
|
27
rustbyexample/flow_control.d/if_let2.rs
Normal file
27
rustbyexample/flow_control.d/if_let2.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
enum Foo {
|
||||||
|
Bar,
|
||||||
|
Baz,
|
||||||
|
Qux(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let a = Foo::Bar;
|
||||||
|
let b = Foo::Baz;
|
||||||
|
let c = Foo::Qux(100);
|
||||||
|
|
||||||
|
if let Foo::Bar = a {
|
||||||
|
println!("a is a foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Foo::Bar = b {
|
||||||
|
println!("b is foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Foo::Qux(value) = c {
|
||||||
|
println!("c is {}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Foo::Qux(value @ 100) = c {
|
||||||
|
println!("c is one hundred (value: {:?})", value);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user