From 10424927b172fd22e09127320df07b17f8ec4f51 Mon Sep 17 00:00:00 2001 From: Peter Harpending Date: Thu, 4 Jun 2026 14:01:46 -0700 Subject: [PATCH] stuff --- cli/scratch/gsc_test_file | 77 +++++++++ cli/scratch/sophia_syntax.md | 292 +++++++++++++++++++++++++++++++ cli/src/gsc_test_file.erl | 324 +++++++++++++++++------------------ src/gsc_signal.erl | 111 ++++++++++++ 4 files changed, 639 insertions(+), 165 deletions(-) create mode 100644 cli/scratch/gsc_test_file create mode 100644 cli/scratch/sophia_syntax.md create mode 100644 src/gsc_signal.erl diff --git a/cli/scratch/gsc_test_file b/cli/scratch/gsc_test_file new file mode 100644 index 0000000..0d3a27e --- /dev/null +++ b/cli/scratch/gsc_test_file @@ -0,0 +1,77 @@ + +-spec s2t_file(Signal) -> AstFile when + Signal :: [tk()], + AstFile :: #ns{meta :: file, kids :: asf()}. + +s2t_file([]) -> + error(empty_file); +s2t_file(S0 = [#tk{pos = {_, FileCol}} | _]) -> + Blk0 = s2t_gulp_block(FileCol, S0), + Blk1 = t2t_parse_tds_in_block(Blk0), + #ns{meta = file, kids = [Blk1]}. + +-spec s2t_gulp_block(BlkCol, Signal) -> Block when + BlkCol :: pos_integer(), + Signal :: [tk()], + Block :: #ns{meta :: block}. + +s2t_gulp_block(BCol, Tks) -> + % sanity check + InBlock = fun(#tk{pos = {_, TCol}}) -> BCol =< TCol end, + true = lists:all(InBlock, Tks), + BlockItems = s2f_block_items(BCol, Tks), + #ns{meta = block, kids = BlockItems}. + +-spec s2f_block_items(BCol, Signal) -> BlkItems when + BCol :: pos_integer(), + Signal :: [tk()], + BlkItems :: [BlkItem], + BlkItem :: #ns{meta :: block_item, + kids :: asf()}. + +s2f_block_items(BCol, Signal) -> + s2f_block_items(BCol, [], Signal). + + +s2f_block_items(_BCol, Stk, []) -> + lists:reverse(Stk); +s2f_block_items(BCol, Stk, [#tk{pos = {_, BCol}} = T0 | F0]) -> + {slurp, BlkItem, F1} = s2t_slurp_block_item(BCol, T0, F0), + s2f_block_items(BCol, [BlkItem | Stk], F1). + + +s2t_slurp_block_item(BCol, T0, F0) -> + {ItemTokens, F1} = s2s_sw_block_item(BCol, T0, F0), + Item = #ns{meta = block_item, kids = ItemTokens}, + {slurp, Item, F1}. + +% sw = splitwith; kind of take/drop +s2s_sw_block_item(BCol, T0, F0) -> + InItem = fun(#tk{pos = {_, TCol}}) -> BCol < TCol end, + {F0_II, F1} = lists:splitwith(InItem, F0), + {[T0 | F0_II], F1}. + +-spec t2t_parse_tds_in_block(Block0) -> Block1 when + Block0 :: ast(), + Block1 :: ast(). + +% go through and convert the block_item nodes to top +% decls +t2t_parse_tds_in_block(B0 = #ns{meta = block, kids = F0}) -> + F1 = lists:map(fun t2t_parse_td_from_item/1, F0), + B0#ns{kids = F1}. + + +-spec t2t_parse_td_from_item(BlockItem) -> TopDecl when + BlockItem :: #ns{meta :: block_item}, + TopDecl :: #ns{meta :: td_meta()}. + +t2t_parse_td_from_item(#ns{meta = block_item, kids = Signal}) -> + s2t_top_decl(Signal). + + +-spec s2t_top_decl(Signal) -> TdTree when + Signal :: [tk()], + TdTree :: ast(). + +s2t_top_decl(S0) -> diff --git a/cli/scratch/sophia_syntax.md b/cli/scratch/sophia_syntax.md new file mode 100644 index 0000000..3f9d84f --- /dev/null +++ b/cli/scratch/sophia_syntax.md @@ -0,0 +1,292 @@ +# Syntax + +## Lexical syntax + +### Comments + +Single line comments start with `//` and block comments are enclosed in `/*` +and `*/` and can be nested. + +### Keywords + +``` +contract include let switch type record datatype if elif else function +stateful payable true false mod public entrypoint private indexed namespace +interface main using as for hiding +``` + +### Tokens + +- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter. +- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter. +- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`) +- `QCon = (Con\.)+Con` qualified constructor +- `TVar = 'Id` type variable (e.g `'a`, `'b`) +- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators +- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators +- `String` string literal enclosed in `"` with escape character `\` +- `Char` character literal enclosed in `'` with escape character `\` +- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix +- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix +- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix + +Valid string escape codes are + +| Escape | ASCII | | +|---------------|-------------|---| +| `\b` | 8 | | +| `\t` | 9 | | +| `\n` | 10 | | +| `\v` | 11 | | +| `\f` | 12 | | +| `\r` | 13 | | +| `\e` | 27 | | +| `\xHexDigits` | *HexDigits* | | + + +See the [identifier encoding scheme](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/node/api/api_encoding.md) for the +details on the base58 literals. + +## Layout blocks + +Sophia uses Python-style layout rules to group declarations and statements. A +layout block with more than one element must start on a separate line and be +indented more than the currently enclosing layout block. Blocks with a single +element can be written on the same line as the previous token. + +Each element of the block must share the same indentation and no part of an +element may be indented less than the indentation of the block. For instance + +```sophia +contract Layout = + function foo() = 0 // no layout + function bar() = // layout block starts on next line + let x = foo() // indented more than 2 spaces + x + + 1 // the '+' is indented more than the 'x' +``` + +## Notation + +In describing the syntax below, we use the following conventions: + +- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with + some associated value (like `Id`). +- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`. +- Choices are separated by vertical bars: `|`. +- Optional elements are enclosed in `[` square brackets `]`. +- `(` Parentheses `)` are used for grouping. +- Zero or more repetitions are denoted by a postfix `*`, and one or more + repetitions by a `+`. +- `Block(X)` denotes a layout block of `X`s. +- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s + separated by `S`s. +- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty. + + +## Declarations + +A Sophia file consists of a sequence of *declarations* in a layout block. + +```c +File ::= Block(TopDecl) + +TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl) + | 'contract' 'interface' Con [Implement] '=' Block(Decl) + | 'namespace' Con '=' Block(Decl) + | '@compiler' PragmaOp Version + | 'include' String + | Using + +Implement ::= ':' Sep1(Con, ',') + +Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias + | 'record' Id ['(' TVar* ')'] '=' RecordType + | 'datatype' Id ['(' TVar* ')'] '=' DataType + | 'let' Id [':' Type] '=' Expr + | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) + | Using + +FunDecl ::= Id ':' Type // Type signature + | Id Args [':' Type] '=' Block(Stmt) // Definition + | Id Args [':' Type] Block(GuardedDef) // Guarded definitions + +GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt) + +Using ::= 'using' Con ['as' Con] [UsingParts] +UsingParts ::= 'for' '[' Sep1(Id, ',') ']' + | 'hiding' '[' Sep1(Id, ',') ']' + +PragmaOp ::= '<' | '=<' | '==' | '>=' | '>' +Version ::= Sep1(Int, '.') + +EModifier ::= 'payable' | 'stateful' +FModifier ::= 'stateful' | 'private' + +Args ::= '(' Sep(Pattern, ',') ')' +``` + +Contract declarations must appear at the top-level. + +For example, +```sophia +contract Test = + type t = int + entrypoint add (x : t, y : t) = x + y +``` + +There are three forms of type declarations: type aliases (declared with the +`type` keyword), record type definitions (`record`) and data type definitions +(`datatype`): + +```c +TypeAlias ::= Type +RecordType ::= '{' Sep(FieldType, ',') '}' +DataType ::= Sep1(ConDecl, '|') + +FieldType ::= Id ':' Type +ConDecl ::= Con ['(' Sep1(Type, ',') ')'] +``` + +For example, +```sophia +record point('a) = {x : 'a, y : 'a} +datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a)) +type int_shape = shape(int) +``` + +## Types + +```c +Type ::= Domain '=>' Type // Function type + | Type '(' Sep(Type, ',') ')' // Type application + | '(' Type ')' // Parens + | 'unit' | Sep(Type, '*') // Tuples + | Id | QId | TVar + +Domain ::= Type // Single argument + | '(' Sep(Type, ',') ')' // Multiple arguments +``` + +The function type arrow associates to the right. + +Example, +```sophia +'a => list('a) => (int * list('a)) +``` + +## Statements + +Function bodies are blocks of *statements*, where a statement is one of the following + +```c +Stmt ::= 'switch' '(' Expr ')' Block(Case) + | 'if' '(' Expr ')' Block(Stmt) + | 'elif' '(' Expr ')' Block(Stmt) + | 'else' Block(Stmt) + | 'let' LetDef + | Using + | Expr + +LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition + | Pattern '=' Block(Stmt) // Value definition + +Case ::= Pattern '=>' Block(Stmt) + | Pattern Block(GuardedCase) + +GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt) + +Pattern ::= Expr +``` + +`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example, + +```sophia +let x : int = 4 +switch(f(x)) + None => 0 + Some(y) => + if(y > 10) + "too big" + elif(y < 3) + "too small" + else + "just right" +``` + +## Expressions + +```c +Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1 + | '(' BinOp ')' // Operator lambda (+) + | 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x + | Expr ':' Type // Type annotation 5 : int + | Expr BinOp Expr // Binary operator x + y + | UnOp Expr // Unary operator ! b + | Expr '(' Sep(Expr, ',') ')' // Application f(x, y) + | Expr '.' Id // Projection state.x + | Expr '[' Expr ']' // Map lookup map[key] + | Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y } + | '[' Sep(Expr, ',') ']' // List [1, 2, 3] + | '[' Expr '|' Sep(Generator, ',') ']' + // List comprehension [k | x <- [1], if (f(x)), let k = x+1] + | '[' Expr '..' Expr ']' // List range [1..n] + | '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val} + | '(' Expr ')' // Parens (1 + 2) * 3 + | '(' Expr '=' Expr ')' // Assign pattern (y = x::_) + | Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token + | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' + | AccountAddress | ContractAddress // Chain identifiers + | Signature // Signature + | '???' // Hole expression 1 + ??? + +Generator ::= Pattern '<-' Expr // Generator + | 'if' '(' Expr ')' // Guard + | LetDef // Definition + +LamArgs ::= '(' Sep(LamArg, ',') ')' +LamArg ::= Id [':' Type] + +FieldUpdate ::= Path '=' Expr +Path ::= Id // Record field + | '[' Expr ']' // Map key + | Path '.' Id // Nested record field + | Path '[' Expr ']' // Nested map key + +BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' + | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' + | 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>' +UnOp ::= '-' | '!' | 'bnot' +``` + +## Operators types + +| Operators | Type +| --- | --- +| `-` `+` `*` `/` `mod` `^` | arithmetic operators +| `!` `&&` `\|\|` | logical operators +| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators +| `==` `!=` `<` `>` `=<` `>=` | comparison operators +| `::` `++` | list operators +| `\|>` | functional operators + +## Operator precedence + +In order of highest to lowest precedence. + +| Operators | Associativity +| --- | --- +| `!` `bnot`| right +| `^` | left +| `*` `/` `mod` | left +| `-` (unary) | right +| `+` `-` | left +| `<<` `>>` | left +| `::` `++` | right +| `<` `>` `=<` `>=` `==` `!=` | none +| `band` | left +| `bxor` | left +| `bor` | left +| `&&` | right +| `\|\|` | right +| `\|>` | left diff --git a/cli/src/gsc_test_file.erl b/cli/src/gsc_test_file.erl index 56d80c5..7fd97a7 100644 --- a/cli/src/gsc_test_file.erl +++ b/cli/src/gsc_test_file.erl @@ -7,16 +7,8 @@ -include("$gsc_include/gsc.hrl"). --type td_target() - :: ct - | iface - | ns - | pragma - | include - | using - . --record(ct, +-record(td_ct, {payable = none :: none | false | {true, tk()}, main = none :: none | false | {true, tk()}, contract = none :: none | tk(), @@ -24,24 +16,46 @@ impls = none :: none | [tk()], eq = none :: none | tk()}). --type td_meta() :: #ct{}. +-type td_meta() :: #td_ct{}. + +-record(decl_type, + {type = none :: none | tk(), + id = none :: none | tk(), + params = none :: none | [tk()], + eq = none :: none | tk()}). + +-type decl_meta() :: #decl_type{}. -type ast_meta() :: file - | block - | block_item | td_meta() + | decl_meta() | nyi | {nyi, any()} . + +-type td_target() + :: td_ct + | td_iface + | td_ns + | td_pragma + | td_include + | using + . + -type s2t_target() :: file - | {block_of, s2t_target()} + | top_decl + | td_target() | nyi | {nyi, any()} . +-type s2f_target() + :: {block_of, s2t_target()} + . + -type ast() :: ntree(ast_meta(), tk()). -type asf() :: nforest(ast_meta(), tk()). @@ -51,7 +65,7 @@ main() -> HelloP = ts_utils:ct_file_abspath(HelloN), {ok, HelloS} = file:read_file(HelloP), S0 = gsc:unsafe_signal_from_file(HelloP), - T1 = s2t_file(S0), + T1 = s2t(file, S0), io:format("hello.aes:~n", []), io:format("```~n", []), io:format("~ts", [HelloS]), @@ -59,16 +73,6 @@ main() -> io:format("AST: ~tp~n", [T1]), ok. - --spec s2t(ParseTarget, Signal) -> Tree when - ParseTarget :: s2t_target(), - Signal :: [tk()], - Tree :: ast(). - -s2t(_, _) -> - error(nyi). - - % // Hello World Contract % // Copyright (c) 2025 QPQ AG % @@ -80,188 +84,147 @@ s2t(_, _) -> % entrypoint hello(): string = % "hello, world" - --spec s2t_file(Signal) -> AstFile when - Signal :: [tk()], - AstFile :: #ns{meta :: file, kids :: asf()}. - -s2t_file([]) -> - error(empty_file); -s2t_file(S0 = [#tk{pos = {_, FileCol}} | _]) -> - Blk0 = s2t_gulp_block(FileCol, S0), - Blk1 = t2t_parse_tds_in_block(Blk0), - #ns{meta = file, kids = [Blk1]}. - - - - --spec s2t_gulp_block(BlkCol, Signal) -> Block when - BlkCol :: pos_integer(), - Signal :: [tk()], - Block :: #ns{meta :: block}. - -s2t_gulp_block(BCol, Tks) -> - % sanity check - InBlock = fun(#tk{pos = {_, TCol}}) -> BCol =< TCol end, - true = lists:all(InBlock, Tks), - BlockItems = s2f_block_items(BCol, Tks), - #ns{meta = block, kids = BlockItems}. - - --spec s2f_block_items(BCol, Signal) -> BlkItems when - BCol :: pos_integer(), - Signal :: [tk()], - BlkItems :: [BlkItem], - BlkItem :: #ns{meta :: block_item, - kids :: asf()}. - -s2f_block_items(BCol, Signal) -> - s2f_block_items(BCol, [], Signal). - - -s2f_block_items(_BCol, Stk, []) -> - lists:reverse(Stk); -s2f_block_items(BCol, Stk, [#tk{pos = {_, BCol}} = T0 | F0]) -> - {slurp, BlkItem, F1} = s2t_slurp_block_item(BCol, T0, F0), - s2f_block_items(BCol, [BlkItem | Stk], F1). - - -s2t_slurp_block_item(BCol, T0, F0) -> - {ItemTokens, F1} = s2s_sw_block_item(BCol, T0, F0), - Item = #ns{meta = block_item, kids = ItemTokens}, - {slurp, Item, F1}. - -% sw = splitwith; kind of take/drop -s2s_sw_block_item(BCol, T0, F0) -> - InItem = fun(#tk{pos = {_, TCol}}) -> BCol < TCol end, - {F0_II, F1} = lists:splitwith(InItem, F0), - {[T0 | F0_II], F1}. - +-spec s2t(ParseTarget, Signal) -> AST when + ParseTarget :: file, + Signal :: [tk()], + AST :: ast(). % File ::= Block(TopDecl) -% +s2t(file, Signal) -> + case Signal of + [] -> error(empty_file); + _ -> {ns, file, s2f({block_of, top_decl}, Signal)} + end; % TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl) % | ['payable'] 'contract' 'interface' Con [Implement] '=' Block(Decl) % | 'namespace' Con '=' Block(Decl) % | '@compiler' PragmaOp Version % | 'include' String % | Using +s2t(top_decl, Signal) -> + NewTarget = + case gsc_tokens:strings(3, Signal) of + ["payable", "contract", "interface"] -> td_iface; + ["contract", "interface" | _] -> td_iface; + ["payable", "main", "contract"] -> td_ct; + ["payable", "contract" | _] -> td_ct; + ["contract" | _] -> td_ct; + ["namespace" | _] -> td_namespace; + ["@compiler" | _] -> td_pragma; + ["include" | _] -> td_include; + ["using" | _] -> using + end, + s2t(NewTarget, Signal); +% ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl) +s2t(td_ct, S0) -> + {slurp, CtMeta, S1} = s2s_slurp_meta(#td_ct{}, S0), + {ns, CtMeta, s2f({block_of, decl}, S1)}; +% Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias +% | 'record' Id ['(' TVar* ')'] '=' RecordType +% | 'datatype' Id ['(' TVar* ')'] '=' DataType +% | 'let' Id [':' Type] '=' Expr +% | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) +% | Using +s2t(decl, S0) -> + NewTarget = + case gsc_tokens:strings(3, S0) of + ["type" | _] -> decl_type; + ["record" | _] -> decl_record; + ["datatype" | _] -> decl_datatype; + ["let" | _] -> decl_let; + Pfx3 -> + IsEp = lists:member("entrypoint", Pfx3), + IsFn = lists:member("function", Pfx3), + if + IsEp -> decl_entrypoint; + IsFn -> decl_function; + true -> error({bad_decl, S0}) + end + end, + s2t(NewTarget, S0); +% 'type' Id ['(' TVar* ')'] '=' TypeAlias +s2t(decl_type, S0) -> + {slurp, Meta, S1} = s2s_slurp_meta(#decl_type{}, S0), + {ns, Meta, s2t(type, S1)}; +s2t(nyi, Signal) -> + {ns, nyi, Signal}; +s2t(NYI = {nyi, _}, Signal) -> + {ns, NYI, Signal}; +s2t(NYI, Signal) -> + {ns, {nyi, NYI}, Signal}. --spec t2t_parse_tds_in_block(Block0) -> Block1 when - Block0 :: ast(), - Block1 :: ast(). +-spec s2f(ForestTarget, Signal) -> Forest when + ForestTarget :: s2f_target(), + Signal :: [tk()], + Forest :: asf(). -% go through and convert the block_item nodes to top -% decls -t2t_parse_tds_in_block(B0 = #ns{meta = block, kids = F0}) -> - F1 = lists:map(fun t2t_parse_td_from_item/1, F0), - B0#ns{kids = F1}. +s2f({block_of, TreeTarget}, S0) -> + {gulp, Items} = gsc_signal:gulp_block_items(S0), + [s2t(TreeTarget, I) || I <- Items]. --spec t2t_parse_td_from_item(BlockItem) -> TopDecl when - BlockItem :: #ns{meta :: block_item}, - TopDecl :: #ns{meta :: td_meta()}. +-spec s2s_slurp_meta(InitMeta, Signal) -> Result when + InitMeta :: Meta, + Signal :: [tk()], + Result :: {slurp, Meta, NewSignal}, + Meta :: ast_meta(), + NewSignal :: Signal. -t2t_parse_td_from_item(#ns{meta = block_item, kids = Signal}) -> - s2t_top_decl(Signal). +s2s_slurp_meta(M = #td_ct{}, S) -> + s2s_sm_td_ct(M, S); +s2s_slurp_meta(M = #decl_type{}, S) -> + s2s_sm_decl_type(M, S); +s2s_slurp_meta(M, S) -> + error({s2s_slurp_meta, M, S}). --spec s2t_top_decl(Signal) -> TdTree when - Signal :: [tk()], - TdTree :: ast(). - -s2t_top_decl(S0) -> - case choose_td_target(S0) of - ct -> - s2t_ct(S0); - iface -> - f2t_nyi(iface, S0); - namespace -> - f2t_nyi(namespace, S0); - pragma -> - f2t_nyi(pragma, S0); - include -> - f2t_nyi(include, S0); - using -> - f2t_nyi(using, S0) - end. - - - --spec choose_td_target(Signal) -> TdTarget when - Signal :: [tk()], - TdTarget :: td_target(). - -choose_td_target(Signal) -> - case gsc_tokens:strings(3, Signal) of - ["payable", "contract", "interface"] -> iface; - ["contract", "interface" | _] -> iface; - ["payable", "main", "contract"] -> ct; - ["payable", "contract" | _] -> ct; - ["contract" | _] -> ct; - ["namespace" | _] -> namespace; - ["@compiler" | _] -> pragma; - ["include" | _] -> include; - ["using" | _] -> using - end. - - - --spec s2t_ct(Signal) -> CtAst when - Signal :: [tk()], - CtAst :: ast(). - -s2t_ct(S0) -> - s2t_ct(#ct{}, S0). - -s2t_ct(Ct = #ct{payable = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{payable = none}, S0) -> case S0 of [#tk{str = "payable"} = T0 | S1] -> - s2t_ct(Ct#ct{payable = {true, T0}}, S1); + s2s_sm_td_ct(Ct#td_ct{payable = {true, T0}}, S1); _ -> - s2t_ct(Ct#ct{payable = false}, S0) + s2s_sm_td_ct(Ct#td_ct{payable = false}, S0) end; -s2t_ct(Ct = #ct{main = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{main = none}, S0) -> case S0 of [#tk{str = "main"} = T0 | S1] -> - s2t_ct(Ct#ct{main = {true, T0}}, S1); + s2s_sm_td_ct(Ct#td_ct{main = {true, T0}}, S1); _ -> - s2t_ct(Ct#ct{main = false}, S0) + s2s_sm_td_ct(Ct#td_ct{main = false}, S0) end; -s2t_ct(Ct = #ct{contract = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{contract = none}, S0) -> case S0 of [#tk{str = "contract"} = T0 | S1] -> - s2t_ct(Ct#ct{contract = T0}, S1); + s2s_sm_td_ct(Ct#td_ct{contract = T0}, S1); _ -> error({no_kwd_contract, Ct, S0}) end; -s2t_ct(Ct = #ct{con = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{con = none}, S0) -> case S0 of [#tk{shape = con} = T0 | S1] -> - s2t_ct(Ct#ct{con = T0}, S1); + s2s_sm_td_ct(Ct#td_ct{con = T0}, S1); _ -> error({no_contract_name, Ct, S0}) end; -s2t_ct(Ct = #ct{impls = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{impls = none}, S0) -> case gsc_tokens:strings(1, S0) of [":"] -> {slurp, Impls, S1} = s2f_slurp_impls(S0), - s2t_ct(Ct#ct{impls = Impls}, S1); + s2s_sm_td_ct(Ct#td_ct{impls = Impls}, S1); _ -> - s2t_ct(Ct#ct{impls = []}, S0) + s2s_sm_td_ct(Ct#td_ct{impls = []}, S0) end; -s2t_ct(Ct = #ct{eq = none}, S0) -> +s2s_sm_td_ct(Ct = #td_ct{eq = none}, S0) -> case S0 of [#tk{str = "="} = T0 | S1] -> - s2t_ct(Ct#ct{eq = T0}, S1); + s2s_sm_td_ct(Ct#td_ct{eq = T0}, S1); _ -> error({no_equal_sign, Ct, S0}) end; -s2t_ct(Ct, S0) -> - #ns{meta = Ct, kids = S0}. - +s2s_sm_td_ct(Ct, S0) -> + {slurp, Ct, S0}. s2f_slurp_impls([#tk{str = ":"}, #tk{shape = con} = I0 | S0]) -> s2f_slurp_impls([I0], S0). @@ -272,8 +235,39 @@ s2f_slurp_impls(Stk, S0) -> {slurp, lists:reverse(Stk), S0}. -f2t_nyi(F) -> - {ns, nyi, F}. +%-record(decl_type, +% {type = none :: none | tk(), +% id = none :: none | tk(), +% params = none :: none | [tk()], +% eq = none :: none | tk()}). -f2t_nyi(Why, F) -> - {ns, {nyi, Why}, F}. +s2s_sm_decl_type(M = #decl_type{type = none}, S0) -> + case S0 of + [#tk{str = "type"} = T0 | S1] -> + s2s_sm_decl_type(M#decl_type{type = T0}, S1); + _ -> + error({no_kwd_type, S0}) + end; +s2s_sm_decl_type(M = #decl_type{id = none}, S0) -> + case S0 of + [#tk{shape = id} = T0 | S1] -> + s2s_sm_decl_type(M#decl_type{id = T0}, S1); + _ -> + error({no_type_id, S0}) + end; +s2s_sm_decl_type(M = #decl_type{params = none}, S0) -> + case S0 of + [#tk{str = "("} = T0 | _] -> + error({fixme, parens_bad}); + _ -> + s2s_sm_decl_type(M#decl_type{params = []}, S0) + end; +s2s_sm_decl_type(M = #decl_type{eq = none}, S0) -> + case S0 of + [#tk{str = "="} = T0 | S1] -> + s2s_sm_decl_type(M#decl_type{eq = T0}, S1); + _ -> + error({no_equal_sign, S0}) + end; +s2s_sm_decl_type(M, S0) -> + {slurp, M, S0}. diff --git a/src/gsc_signal.erl b/src/gsc_signal.erl new file mode 100644 index 0000000..c26a4b6 --- /dev/null +++ b/src/gsc_signal.erl @@ -0,0 +1,111 @@ +% signal = non-noisy tokens +-module(gsc_signal). + +-export([ + from_tokens/1, + is_block/1, + gulp_block_items/1, + block_to_items/1, + take_block_item/1 +]). + +-include("$gsc_include/gsc.hrl"). + + +-spec from_tokens(Tokens) -> Signal when + Tokens :: [tk()], + Signal :: [tk()]. +% @doc filter out comments/whitespace + +from_tokens(Tokens) -> + gsc_tokens:filter_significant(Tokens). + + + +-spec is_block(Signal) -> Result when + Signal :: [tk()], + Result :: boolean(). + +is_block([]) -> + true; +is_block([#tk{pos = {_, BCol}} | Rest]) -> + InBlock = + fun(#tk{pos = {_, TCol}}) -> + BCol =< TCol + end, + lists:all(InBlock, Rest). + + + +-spec gulp_block_items(Signal) -> Result when + Signal :: [tk()], + Result :: {slurp, Items, NewSignal} + | {error, any()}, + Items :: [Signal], + NewSignal :: Signal. + +gulp_block_items(S) -> + case is_block(S) of + true -> {gulp, block_to_items(S)}; + false -> find_badness(S) + end. + +find_badness([#tk{pos = {_, StartCol}} = StartTk | Rest]) -> + find_badness(StartCol, StartTk, Rest). + +find_badness(StartCol, StartTk, [#tk{pos = {_, TkCol}} = Tk | Rest]) -> + Bad = TkCol < StartCol, + case Bad of + false -> find_badness(StartCol, StartTk, Rest); + true -> {error, {bad_block, [{start_col, StartCol}, + {end_col, TkCol}, + {start_tk, StartTk}, + {end_tk, Tk}]}} + end. + + + +-spec block_to_items(Signal) -> BlockItems when + Signal :: [tk()], + BlockItems :: [Signal]. +% @doc +% naive algorithm, so doesn't ensure all block items +% are same indent level +% +% Input: +% foo = ... +% bar = ... +% baz = ... +% +% Output: +% [foo = ..., +% bar = ..., +% baz = ...] +block_to_items([]) -> + []; +block_to_items(S) -> + b2is([], S). + +b2is(Acc, []) -> + lists:reverse(Acc); +b2is(Acc, S) -> + {Item, S1} = take_block_item(S), + b2is([Item | Acc], S1). + + + +-spec take_block_item(Signal) -> Result when + Signal :: [tk()], + Result :: {Item, NewSignal}, + Item :: Signal, + NewSignal :: Signal. + +take_block_item([]) -> + {[], []}; +take_block_item([#tk{pos = {_, ICol}} = T0 | S0]) -> + InItem = + fun(#tk{pos = {_, TCol}}) -> + ICol < TCol + end, + {S0_II, S1} = lists:splitwith(InItem, S0), + {[T0 | S0_II], S1}.