• Text on FSM

    From jmariano@21:1/5 to All on Thu Mar 9 14:17:14 2023
    Hello
    Does anyone know of a nice text on finite state machines and their software implementation on embedded systems?

    I'm looking for some theoretical background and design methodology. A few examples of "C" implementation would be a nice but not really needed. I'm not looking for a recipe or code but for a more formal explanation on the workings of FSM.
    Thanks
    jmariano

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rick C@21:1/5 to jmariano on Thu Mar 9 16:52:06 2023
    On Thursday, March 9, 2023 at 5:17:18 PM UTC-5, jmariano wrote:
    Hello
    Does anyone know of a nice text on finite state machines and their software implementation on embedded systems?

    I'm looking for some theoretical background and design methodology. A few examples of "C" implementation would be a nice but not really needed. I'm not looking for a recipe or code but for a more formal explanation on the workings of FSM.

    FSM are pretty simple. They usually use a directed graph (drawing with circles for states and arrows for transitions between states) to represent the design. It's always a good idea to start with that. Give the sames names, or values and put the
    inputs on the transitions. When a state is not changing, this should be represented with an arrow from the state back to itself. But it's not important to show these for the code. It does make it clear what's happening. Some states are transitory and
    do not remain in the same state. This helps you spot when you've missed an input condition.

    The inputs to the FSM are the "inputs" (duh) but also the "current state". The FSM calculates "next states" and "outputs". That's a FSM in its simplest form.

    The next state always depends on the current state and the inputs. The output can depend on the present state only, or can also change depending on inputs. In that case, I think of the outputs as being part of the "state" of the FSM, but the next
    state won't depend on outputs. This can be a bit complex, so it may be simpler to start with a FSM where the outputs only depend on the current state.

    The code is typically written as a CASE statement on the present state. Within each present state of the CASE, code is written to examine the relevant inputs and decide the next state or possibly also outputs if you are doing that. Your outputs don't
    have to be calculated in the CASE statement. They can be calculated only on the present state.

    That's it in a nutshell. I'm used to hardware, coding in VHDL, but it's the same thing in C or whatever. You just have to watch where you put what code since the order matters in C. In VHDL sections of code all run in parallel, since it's hardware.

    Sorry I don't have a reference. I spent a lot of time reading about FSM coding. But most of it was a bit pedantic. For example, the theoretical analysis that was done nearly 100 years ago, resulted in the Mealy vs. Moore classification, depending on
    if the outputs depend on the inputs or just the present state. I never remember which is which because very few people use either. Instead they use a hybrid where the outputs are calculated in the same case statement, which means they are a cycle late
    if done with the classical approach.

    Whatever. Just pay attention to the timing of your output changes, and if you want to have the next state depend on an output, move that output into part of the state, since that is what it is.

    I hope this helped.

    --

    Rick C.

    - Get 1,000 miles of free Supercharging
    - Tesla referral code - https://ts.la/richard11209

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to jmariano on Thu Mar 9 19:11:08 2023
    On 3/9/2023 3:17 PM, jmariano wrote:
    Hello Does anyone know of a nice text on finite state machines and their software implementation on embedded systems?

    Implementations can vary widely. Do you want to present (current state, inputs) to a machine and have (next_state, outputs) emitted "immediately"?
    Or, is the processing time not critical (e.g., UI's tend to be this type)

    Do you want to limit the machine to the "classic" design? Or, add
    extensions (e.g., support the notion of "previous_state", "subroutines",
    etc.)?

    I'm looking for some theoretical background and design methodology. A few examples of "C" implementation would be a nice but not really needed. I'm
    not looking for a recipe or code but for a more formal explanation on the workings of FSM. Thanks jmariano

    In the degenerate case, you build a matrix that is accessed by [state][inputs] and delivers (next_state, outputs). But, it's obvious that the size of
    this structure grows quickly with number of states and inputs. In practice, often a state may have only a few significant inputs that govern the choice
    of next state so the matrix contains lots of redundant entries.

    You can unfold the matrix into a series of switch/case statements -- but,
    I've found that makes it hard to sort out what's really happening (the
    beauty of a state machine is that it is concise).

    I prefer representations like:

    Case IDLE
    On <digit> GoTo ACCEPTING Executing GobbleDigit() On <clear> GoTo ISSUE_PROMPT Executing ClearValue()
    On <enter> GoTo TEST_VALUE Executing CheckLimits() ..

    Note that there are only 3 items encoded on each line:
    - the input being examined
    - the name of the intended next_state
    - the action to be performed *in* the transition
    As such, this can be encoded in as few as 3 bytes (depending on how
    many states, inputs, and actions you need to support)

    But, the big advantage is it's concise -- no extra syntactic sugar
    to clutter up the page (cuz you want to express the machine in
    as little space as possible as it gets harder to chase layers of
    case/switch statements with interspersed *actions*)

    [There are also UML techniques for their representation and tools that will parse such descriptions and build the code for you]

    In school, Hill & Peterson was our reference (_Intro to Switching Theory and Logical Design_) but you don't need much "text" to understand the concepts (assuming you already understand logic).

    OTOH, it's worth learning about minimization techniques -- esp if your
    approach to the machine's design is /ad hoc/ (ripe for hidden optimizations).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Bill Davy@21:1/5 to Don Y on Fri Mar 10 08:37:45 2023
    On 10/03/2023 02:11, Don Y wrote:
    On 3/9/2023 3:17 PM, jmariano wrote:
    Hello Does anyone know of a nice text on finite state machines and their
    software implementation on embedded systems?

    Implementations can vary widely.  Do you want to present (current state, inputs) to a machine and have (next_state, outputs) emitted "immediately"? Or, is the processing time not critical (e.g., UI's tend to be this type)

    Do you want to limit the machine to the "classic" design?  Or, add extensions (e.g., support the notion of "previous_state", "subroutines", etc.)?

    I'm looking for some theoretical background and design methodology. A few
    examples of "C" implementation would be a nice but not really needed. I'm
    not looking for a recipe or code but for a more formal explanation on the
    workings of FSM. Thanks jmariano

    In the degenerate case, you build a matrix that is accessed by [state][inputs]
    and delivers (next_state, outputs).  But, it's obvious that the size of
    this structure grows quickly with number of states and inputs.  In
    practice,
    often a state may have only a few significant inputs that govern the choice of next state so the matrix contains lots of redundant entries.

    You can unfold the matrix into a series of switch/case statements -- but, I've found that makes it hard to sort out what's really happening (the
    beauty of a state machine is that it is concise).

    I prefer representations like:

        Case  IDLE
    On    <digit>        GoTo    ACCEPTING    Executing    GobbleDigit()
    On    <clear>        GoTo    ISSUE_PROMPT    Executing    ClearValue()
    On    <enter>        GoTo    TEST_VALUE    Executing    CheckLimits()
    ..

    Note that there are only 3 items encoded on each line:
    - the input being examined
    - the name of the intended next_state
    - the action to be performed *in* the transition
    As such, this can be encoded in as few as 3 bytes (depending on how
    many states, inputs, and actions you need to support)

    But, the big advantage is it's concise -- no extra syntactic sugar
    to clutter up the page (cuz you want to express the machine in
    as little space as possible as it gets harder to chase layers of
    case/switch statements with interspersed *actions*)

    [There are also UML techniques for their representation and tools that will parse such descriptions and build the code for you]

    In school, Hill & Peterson was our reference (_Intro to Switching Theory
    and
    Logical Design_) but you don't need much "text" to understand the concepts (assuming you already understand logic).

    OTOH, it's worth learning about minimization techniques -- esp if your approach to the machine's design is /ad hoc/ (ripe for hidden
    optimizations).


    I went to a course of lectures on (and by) Harel state-charts.

    Here is one for text: https://github.com/cepsdev/machines4ceps

    There is also https://www.codeproject.com/Articles/11398/A-Lightweight-Implementation-of-UML-Statecharts-in

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pozz@21:1/5 to All on Fri Mar 10 09:54:23 2023
    Il 09/03/2023 23:17, jmariano ha scritto:
    Hello
    Does anyone know of a nice text on finite state machines and their software implementation on embedded systems?

    I'm looking for some theoretical background and design methodology. A few examples of "C" implementation would be a nice but not really needed. I'm not looking for a recipe or code but for a more formal explanation on the workings of FSM.
    Thanks
    jmariano

    Search for Quantum Leaps. Miro Samek has written a very good book about state-machines for embedded systems with an implementation. However it
    isn't free of use, I think. But the book should be now free to download.

    Hierarchical state-machines are a very interesting argument for a system
    that can be modeled as an event-driven system. An embedded systems can
    be usually described as an event-driver system.

    There will be a time when the programmer would simply draw one or more state-machines and click a button to generate the full working code in
    whatever language.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Bill Davy on Fri Mar 10 03:50:45 2023
    On 3/10/2023 1:37 AM, Bill Davy wrote:
    I went to a course of lectures on (and by) Harel state-charts.

    Here is one for text: https://github.com/cepsdev/machines4ceps

    There is also https://www.codeproject.com/Articles/11398/A-Lightweight-Implementation-of-UML-Statecharts-in

    The problem I see with using state machines to control processes
    is that those processes. however trivial they may APPEAR to be, often have
    lots of exceptions that are inherent in their *correct* implementation.

    Representing these in the state machine definition IN A WAY THAT DOESN'T
    CAUSE THEIR SIGNIFICANCE TO BE LOST (resulting in bugs!) becomes challenging.

    Few applications are simple models of DFA -- e.g., the grammar for
    a "numerical value". There are invariably other things going on *during*
    the processing of that DFA (which has no notion of time even though
    the application may be "human driven") that compete with and
    potentially override it!

    We bought a range, recently. It was less than an hour before I
    was able to stumble over a bug in their implementation -- because
    the implementer likely ASSUMED each segment of the grammar would
    operate essentially without disturbance. So, if the user wanted to
    change the temperature setting, this set of states (and state transitions) *looks* like it will achieve that goal... except it doesn't take
    into account that it takes some amount (UNCONSTRAINED!) of time
    for the user to perform those steps (generate the events that
    are prescribed by it).

    And, that while he is faithfully following the prescribed actions,
    other "stuff" can be happening. Like, the cook timer expiring
    and expecting acknowledgement. Oh, but how do you tell the timer
    that THIS "one big single control" activation is intended to acknowledge
    that event and *not* a step in the "change temperature" event
    sequence?

    And, what would happen if one leg of the AC mains "failed" (or
    appeared to) during these two overlapping sequences? Would
    the display be commandeered to indicate that failure? And,
    the acknowledgement of that alert be confused with these
    other two competing activities? Some other yet to be
    discovered "fault"? How do you express the priority of
    those events without makin ghte simple state machine look
    overly complex (have I handled the possibility of a partial
    AC mains failure at THIS point in the machine? SHOULD I??)

    And, what if the cook timer for the *second* oven expired
    while all this was happening? Or, the general purpose ("egg")
    timer?

    There are ways to resolve these problems (DFA hierarchies).
    But, it's too easy for the programmer to miss their potential
    conflicts, mistakenly thinking he's enumerated all of the input
    events, etc. as is required in a "state machine".

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to pozz on Fri Mar 10 04:17:50 2023
    On 3/10/2023 1:54 AM, pozz wrote:
    Hierarchical state-machines are a very interesting argument for a system that can be modeled as an event-driven system. An embedded systems can be usually described as an event-driver system.

    DFA have application besides handling "events". E.g., you can think
    of every character/octet in a message as an "event" (even though they all "appear" as a coherent unit) and use a DFA to parse the content for validity/meaning.

    There will be a time when the programmer would simply draw one or more state-machines and click a button to generate the full working code in whatever
    language.



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Robert Roland@21:1/5 to pozz on Fri Mar 10 12:48:09 2023
    On Fri, 10 Mar 2023 09:54:23 +0100, pozz <pozzugno@gmail.com> wrote:

    There will be a time when the programmer would simply draw one or more >state-machines and click a button to generate the full working code in >whatever language.

    When I went to school, 30 or so years ago, we did have such a program.
    I am not able to remember its name, though.
    --
    RoRo

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rick C@21:1/5 to Robert Roland on Fri Mar 10 07:20:14 2023
    On Friday, March 10, 2023 at 6:48:13 AM UTC-5, Robert Roland wrote:
    On Fri, 10 Mar 2023 09:54:23 +0100, pozz <pozz...@gmail.com> wrote:

    There will be a time when the programmer would simply draw one or more >state-machines and click a button to generate the full working code in >whatever language.
    When I went to school, 30 or so years ago, we did have such a program.
    I am not able to remember its name, though.

    The problem these have, like many graphic oriented approaches, is continuing support and version control of the source. I've never worked with a state machine that was so complex it required anything other than a diagram drawn up in your favorite word
    processor drawing package. Typically, FSM can be decomposed into multiple distinct FSM that are easier to understand, and typically relate to the problem better.

    A FSM with 10 states is easy to code. A FSM with 100 states is probably several FSMs, mistakenly combined into one.

    --

    Rick C.

    + Get 1,000 miles of free Supercharging
    + Tesla referral code - https://ts.la/richard11209

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From StateMachineCOM@21:1/5 to All on Fri Mar 10 07:54:15 2023
    This question is precisely what I've been exploring, working on, and writing about for decades.

    Unfortunately, like most terms in our discipline of embedded programming, the term FSM means different things to different people. That's why you will get many different answers because some people mean "input-driven" state machines, others mean "event-
    driven" state machines, and yet others mean everything in between. Of course, all of this means different implementations, from nested-switch, through state tables, OO State design patterns, various state machine "compilers", etc.

    I tried to cut through this confusion in my video playlist "State Machines" on YouTube:

    https://www.youtube.com/playlist?list=PLPW8O6W-1chxym7TgIPV9k5E8YJtSBToI

    I've also collected a lot of resources about state machines, including my books and papers about the subject. To find out more, just google "Miro Samek".
    --MMS

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Robert Roland on Fri Mar 10 09:39:46 2023
    On 3/10/2023 4:48 AM, Robert Roland wrote:
    On Fri, 10 Mar 2023 09:54:23 +0100, pozz <pozzugno@gmail.com> wrote:

    There will be a time when the programmer would simply draw one or more
    state-machines and click a button to generate the full working code in
    whatever language.

    When I went to school, 30 or so years ago, we did have such a program.
    I am not able to remember its name, though.

    You can use regex "compilers" to deal with DFAs.

    The problem with all of these approaches is they add another "tool" to
    the development process -- and another opportunity for the developer
    (who only uses the tool for a *portion* of a project) to make mistakes
    in its application. The more capable and expressive the tool, the
    more knowledge is required of the developer to exploit its capabilities.

    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory? Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    Instead (IMO), you want something that lets a developer use a technology without reliance on a particular tool (that may not be well-supported
    or may have latent bugs that haven't yet been tickled).

    As such, understanding the technique is more important than finding a tool
    that may )or may not) address your needs. ("I want to write in Eiffel.
    Does your tool output Eiffel source?" Next week it may be some other
    /langue du jour/)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Don Y on Fri Mar 10 10:02:23 2023
    On 3/10/2023 9:39 AM, Don Y wrote:

    The problem with all of these approaches is they add another "tool" to
    the development process -- and another opportunity for the developer
    (who only uses the tool for a *portion* of a project) to make mistakes
    in its application.  The more capable and expressive the tool, the
    more knowledge is required of the developer to exploit its capabilities.

    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory?  Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    By way of example, what does this:
    ^0*(1(00)*10*|10(00)*1(00)*(11)*0(00)*10*)*0*$
    do over the set of binary integers?

    [assuming *I* haven't botched it! I should test it...]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From jmariano@21:1/5 to All on Fri Mar 10 09:51:29 2023
    Hello again!

    Thank you all very much for your valuable inputs! I have A LOT of reading to do!

    I'm not a professional developer, like most of you (I guess). I'm just a "gizmo" builder, serving mainly my own research and my colleagues. I don't have access to professional development tools. My background is in physics, although I took a few
    electronics courses in college. I took courses on embedded systems, usually called "microprocessor in physics" and the like, but the emphasis of these courses tended to be on the digital part and not so much on the computational aspects of the thing.
    That is why I usually get lost on the jargon of computer science and know very little of data structures and things like that.....

    My first contact with FSM was many years ago when I was finishing my PhD. I had to implement a FSM on a FPGA and, of course, I was late, so I pick an example somewhere and just implemented, without really knowing what I was doing....

    Now I would like to finally understand a little better how these things work.

    Also, the sort of things that I build, usually have some type of "controller" in it, and usually talk to a PC via serial interface. This, it seems to me, could to be well modeled by a FSM. I understand that this representation might bring additional
    issues, as it was mentioned by Don Y, but my main goal here is to go one step further from the clumsy programming that I've been doing for the past few years into a bit more formal representation of the problems.

    I also have some questions about synchronization of several FSM, but this is a subject for a feature post.

    Cheers,
    jmariano

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ed Prochak@21:1/5 to Don Y on Fri Mar 10 10:10:16 2023
    On Friday, March 10, 2023 at 11:39:57 AM UTC-5, Don Y wrote:
    On 3/10/2023 4:48 AM, Robert Roland wrote:
    On Fri, 10 Mar 2023 09:54:23 +0100, pozz <pozz...@gmail.com> wrote:

    There will be a time when the programmer would simply draw one or more
    state-machines and click a button to generate the full working code in
    whatever language.

    When I went to school, 30 or so years ago, we did have such a program.
    I am not able to remember its name, though.
    You can use regex "compilers" to deal with DFAs.

    The problem with all of these approaches is they add another "tool" to
    the development process -- and another opportunity for the developer
    (who only uses the tool for a *portion* of a project) to make mistakes
    in its application. The more capable and expressive the tool, the
    more knowledge is required of the developer to exploit its capabilities.

    While you're last statement is true, I do not think it is a valid argument against
    adding another tool to the development process. In a work (not hobby) environment, I expect good management and teams to make considered
    choices about what tools to apply. If a tool is feature rich and well documented,
    then the knowledge should be available (either in you head or in the manual)
    to make the job easier.


    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory? Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    Yes.
    When I have done a lot of regex work I kept a quick reference card handy.

    Instead (IMO), you want something that lets a developer use a technology without reliance on a particular tool (that may not be well-supported
    or may have latent bugs that haven't yet been tickled).

    Being tool agnostic is an ideal goal. In a practice, you must pick some specific tools to get the work done within schedule, budget, and quality constraints.

    If you want portability of design then that should be an explicit fourth constraint.
    Most projects select tools with the additional LONG TERM constraint of
    support throughout the life of the product or product line.

    As such, understanding the technique is more important than finding a tool that may )or may not) address your needs. ("I want to write in Eiffel.
    Does your tool output Eiffel source?" Next week it may be some other
    /langue du jour/)

    Exactly why an abstracting tool that is more focused on the design is better than
    a specific tool.

    State machine design tools are a good example. With a graphic design tool,
    it is much easier to spot missing states or incorrect transitions. It can be clear enough that even end users can understand and point out flaws or enhancements. I don't think the particular output language is the point here.

    BTW, lots of your earlier comments match closely to what I would have posted. This one just struck me are being a little too idealistic.

    Ed

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Ed Prochak on Fri Mar 10 12:10:19 2023
    On 3/10/2023 11:10 AM, Ed Prochak wrote:
    The problem with all of these approaches is they add another "tool" to
    the development process -- and another opportunity for the developer
    (who only uses the tool for a *portion* of a project) to make mistakes
    in its application. The more capable and expressive the tool, the
    more knowledge is required of the developer to exploit its capabilities.

    While you're last statement is true, I do not think it is a valid argument against
    adding another tool to the development process. In a work (not hobby) environment, I expect good management and teams to make considered
    choices about what tools to apply. If a tool is feature rich and well documented,
    then the knowledge should be available (either in you head or in the manual) to make the job easier.

    You have to decide if the effort to learn (and remain "current")
    the tool offsets the advantages gained by using it. I see lots
    of developers who "almost" know how to use something. IMO, this
    is worse than *not* knowing hot to use it (or, not *having* the
    tool) because it breeds a false sense of confidence in their
    efforts.

    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory? Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    Yes.
    When I have done a lot of regex work I kept a quick reference card handy.

    The qualifier on that last statement is the issue. What about
    when you HAVEN'T done work with a tool for some period of time?
    Will you admit to yourself that you likely need a "refresher"?
    Or, will you stumble along and *hope* it "comes back to you"?
    Error-free?

    E.g., are multidimensional arrays stored in row or column major order?
    There are too many "little details" like this that only stay fresh
    in your mind with "frequent refreshing".

    [I write a lot of formal documentation. Yet, I'll be damned if I can
    remember the shortcut for "non-breaking hyphenation" -- despite using
    it dozens of times on any given document! (tomorrow, I'll look it up,
    again!)]

    Instead (IMO), you want something that lets a developer use a technology
    without reliance on a particular tool (that may not be well-supported
    or may have latent bugs that haven't yet been tickled).

    Being tool agnostic is an ideal goal. In a practice, you must pick some specific tools to get the work done within schedule, budget, and quality constraints.

    If you want portability of design then that should be an explicit fourth constraint.
    Most projects select tools with the additional LONG TERM constraint of support throughout the life of the product or product line.

    I've made a lot of money addressing the needs of clients who banked on
    a set of tools, only to discover that they were "no longer supported"
    (e.g., doesn't run unde new version of OS, requires hardware that PCs no
    longer include, etc.)

    You don't realize this is a legitimate design issue until you get
    bitten by it. And, at that time, the time and $$$ available are
    seldom what you'd need.

    As such, understanding the technique is more important than finding a tool >> that may )or may not) address your needs. ("I want to write in Eiffel.
    Does your tool output Eiffel source?" Next week it may be some other
    /langue du jour/)

    Exactly why an abstracting tool that is more focused on the design is better than
    a specific tool.

    What better than a human brain?

    State machine design tools are a good example. With a graphic design tool,
    it is much easier to spot missing states or incorrect transitions. It can be clear enough that even end users can understand and point out flaws or enhancements. I don't think the particular output language is the point here.

    But you can use a pencil and paper (or drawing program) to make
    such a diagram and "see" the same missing states/incorrect transitions.
    The tool just automates binding the design to a particular implementaion.

    BTW, lots of your earlier comments match closely to what I would have posted. This one just struck me are being a little too idealistic.

    Have you looked at Harel charts? For *complex*/layered machines?
    I suspect revisiting one that you managed to *coax* together at
    the start of a project a year or two later (maintenance) would
    leave you wondering what all of the cryptic notation means.

    Would you admit ignorance and "play it safe" -- and expect to have
    time to refamiliarize yourself with it BEFORE trying to make
    changes? Or, would you proceed with a false set of confidence
    and *hope* you don't break anything along the way?

    Because we all KNOW that we have more than adequate time to
    devote to "doing it right", right? :>

    [This is why I push the "idealistic" because in practice is far from it]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to jmariano on Fri Mar 10 12:46:57 2023
    On 3/10/2023 10:51 AM, jmariano wrote:
    My first contact with FSM was many years ago when I was finishing my PhD. I had to implement a FSM on a FPGA and, of course, I was late, so I pick an example somewhere and just implemented, without really knowing what I was doing....

    Now I would like to finally understand a little better how these things
    work.

    Remember, a microprocessor based system *is* a FSM. The "state"
    is the union of all of the bits of storage in the product. The
    "machine" transitions between states (i.e., alters some of those
    bits) based on opcodes and sensed/stored values.

    In the current context, an FSM is a much simpler machine with
    (usually) less state and fewer I/Os.

    Once you start *thinking* in terms of state machines (states),
    many problems become more easily conceptualized.

    For (trivial) example, assume you have some buttons/switches
    that you are monitoring (keyboard?). Unless wetted, most
    mechanical switches bounce as they move from one "state"
    to another (open<->closed).

    Debouncing such switches is a common activity. Typically,
    you "see" a change (closed->open or open->closed) and
    start a timer (implicit or explicit) that allows you to
    ignore any "bouncing" in the immediate future as a known
    consequence of this characteristic of the switch(es).

    When do you recognize the switch as being in the new
    (physical) state? Do you signal the transition as
    being detected as soon as you see the physical state differing
    from your current model of that state ("I think it's open
    but now I see a closure...")? Or, do you wait until you
    are *sure* that the switch will actually settle in that
    new physical "condition" (avoiding the use of "state")?

    If you model this as a set of *four* FSM states:
    - switch IS open
    - switch IS closed
    - switch opening
    - switch closing
    you can better think through how you want to handle that
    bit of information -- and when.

    In the "IS" states, when you sense the physical state of the
    switch as being different from your current assessment, you
    can transition to the corresponding "*ing" state after starting
    a timer. While in that transitional state, you ignore the
    physical switch and just wait for the timer to expire.
    At that point ("timer_expired") you can examine the switch's
    physical state and, if it has returned to the original state,
    choose to *ignore* this bit of noise. Or, if it has settled in the
    "other" state, you can enter that "IS" state and repeat the process
    from the other vantage point.

    Now, you are free to decide which transition(s) to emit the
    "switch_activity" event. Or, change them, even if you opt
    for an asymetric signaling scheme (i.e., signal open-close
    as soon as detected but only signal close->open after the
    timer has expired and you are sure the switch HAS opened)

    Also, the sort of things that I build, usually have some type of
    "controller" in it, and usually talk to a PC via serial interface. This, it seems to me, could to be well modeled by a FSM. I understand that this representation might bring additional issues, as it was mentioned by Don Y, but my main goal here is to go one step further from the clumsy programming that I've been doing for the past few years into a bit more formal representation of the problems.

    Make up some simple problems and draw the state diagrams.

    How would you accept inputs of the ten digit keys, decimal, clear
    and enter to accept a valid decimal number? (note you can't have
    two decimal points -- but, you aren't required to have *any*!)

    As you are designing the state machine, you have to face decisions
    like:
    - does CLEAR remove the last entered keystroke? Or, does it
    discard *all* accumulated keystrokes?
    - what if the user doesn't type ANY digits but just hits ENTER?
    - what limits the number of digits that can be typed? Can you
    handle the value 1092039572397013710293014038402938402938402?
    (if not, how do you deal with that possibility?)

    Do you issue a prompt to the user before accepting the keystrokes?
    Does this prompt remain visible WHILE the user is entering those
    digits? (what if you have a *single-line* display?) Did you
    remember to reemit the prompt if the user clears the entry? etc.

    I also have some questions about synchronization of several FSM, but this is a subject for a feature post.

    A *feature* post! Yay! "Let's go out to the lobby, let's go out to the
    lobby, lets' go out to the lobby, and have ourselves a snack!"



    The synchronization problem exists even with a single machine...
    because there's usually other stuff going on in the product
    that the FSM will have to interact with. So, the "outputs"
    may have side effects -- that are important.

    E.g., if pressing a button (after it has been debounced!!)
    is supposed to start a motor, does the FSM need to
    know that the motor has actually *been* started? I.e.,
    did the "action" that was invoked on the "button debounced"
    transition actually turn on the motor? Or, did it simply
    set up something that will actually do that? In the latter
    case, what assurance do you have that the next time the FSM
    "runs" (i.e., examines inputs) that the code that turns the
    motor on will actually have executed?

    [I design my state machines so that the machine "clears"
    an input/event when it has processed it. This acts as
    a signal to whatever *posted* that event that the event
    has been "consumed. E.g., a keyboard handler can maintain
    a queue of keystrokes and simply block, waiting for the
    "most recently SIGNALED keystroke" to be consumed by the
    FSM. When it sees that feedback, it can place the next
    keystroke (event/input) as the "most recently SIGNALED
    keystroke" and sleep, again.]

    [[I also believe in promoting all major forks in an
    algorithm to first class events/decision states.
    E.g., when I've accepted a set of keystrokes and
    need to verify that the value entered is correct
    (in an acceptable range), I move from the last digit
    accumulating state -- on detecting ENTER -- to a
    "decision state". The action performed on detecting
    ENTER is check_for_valid_value(). EVENTUALLY, an
    indication of the validity of the entry is signalled
    (as an *event*) to the FSM. So, sitting in the
    decision state, it has only two ways to progress
    based on those synthetic events: VALID_VALUE or
    INVALID_VALUE. This makes it easy for a developer to
    see 1) that the value *is* checked 2) how the value is
    handled if good vs. bad. The alternative is to bury
    these tests amongst a bunch of semicolons so the
    developer has to hunt for them]]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Don Y on Fri Mar 10 12:49:05 2023
    On 3/10/2023 12:10 PM, Don Y wrote:
    Because we all KNOW that we have more than adequate time to
    devote to "doing it right", right?  :>

    [This is why I push the "idealistic" because in practice is far from it]

    To be fair, I work with lots of different clients/projects so
    there's no "established" toolset that I can embrace. And I
    can't always coerce the client to purchase/adopt the tools that
    I've found effective. Hence the need to "understand the
    technology" so I can leverage whatever tools I'm *allowed*
    to use. :-/

    OTOH, it makes for a much greater variety of assignments! :>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rick C@21:1/5 to jmariano on Fri Mar 10 11:27:54 2023
    On Friday, March 10, 2023 at 12:51:33 PM UTC-5, jmariano wrote:
    Hello again!

    Thank you all very much for your valuable inputs! I have A LOT of reading to do!

    I'm not a professional developer, like most of you (I guess). I'm just a "gizmo" builder, serving mainly my own research and my colleagues. I don't have access to professional development tools. My background is in physics, although I took a few
    electronics courses in college. I took courses on embedded systems, usually called "microprocessor in physics" and the like, but the emphasis of these courses tended to be on the digital part and not so much on the computational aspects of the thing.
    That is why I usually get lost on the jargon of computer science and know very little of data structures and things like that.....

    My first contact with FSM was many years ago when I was finishing my PhD. I had to implement a FSM on a FPGA and, of course, I was late, so I pick an example somewhere and just implemented, without really knowing what I was doing....

    Now I would like to finally understand a little better how these things work.

    Also, the sort of things that I build, usually have some type of "controller" in it, and usually talk to a PC via serial interface. This, it seems to me, could to be well modeled by a FSM. I understand that this representation might bring additional
    issues, as it was mentioned by Don Y, but my main goal here is to go one step further from the clumsy programming that I've been doing for the past few years into a bit more formal representation of the problems.

    I also have some questions about synchronization of several FSM, but this is a subject for a feature post.

    This is a very simple topic, that can be made as complicated as you wish. It would be good to work through an example of yours, but until you provide one, here's a traffic light.

    Assume there are separate timers and that inputs from sensors are all processed to be "clean". This is a pseudo language rather than any specific language. Because Google Groups does not preserve multiple spaces, I'm using underlines for indentation.
    The states are EW_GREEN, EW_YELLOW, NS_GREEN, NS_YELLOW. The inputs are EW_CAR, NS_CAR and the timer done indicators. Outputs are NS_RED_light, NS_YELLOW_light, NS_GREEN_light, EW_RED_light, EW_YELLOW_light, EW_GREEN_light, and the timer start signals.
    This intersection has more traffic in the NS direction, so that direction gets the green light until it times out (longer time than the EW time) or after a minimum delay, an EW car is detected.

    cur_state CASE
    __ NS_GREEN OF
    ____ IF (NS_LONG_TIMER_done OR (NS_SHORT_TIMER_done AND EW_CAR_detected) ) THEN ______ cur_state <= NS_YELLOW;
    ______ NS_GREEN_light <= OFF;
    ______ NS_YELLOW_light <= ON;
    ______ NS_LONG_TIMER_enable <= OFF;
    ______ NS_SHORT_TIMER_enable <= OFF;
    ______ YELLOW_TIMER_enable <= ON;
    ____ ENDIF ;
    __ ENDOF ;

    __ NS_YELLOW OF
    ____ IF (YELLOW_TIMER_done) THEN
    ______ cur_state <= EW_GREEN;
    ______ NS_YELLOW_light <= OFF;
    ______ NS_RED_light <= ON;
    ______ EW_RED_light <= OFF;
    ______ EW_GREEN_light <= ON;
    ______ YELLOW_TIMER_enable <= OFF;
    ______ EW_TIMER_enable <= ON;
    ____ ENDIF ;
    __ ENDOF ;

    __ EW_GREEN OF
    ____ IF (EW_TIMER_done) THEN
    ______ cur_state <= EW_YELLOW;
    ______ EW_GREEN_light <= OFF;
    ______ EW_YELLOW_light <= ON;
    ______ EW_TIMER_enable <= OFF;
    ______ YELLOW_TIMER_enable <= ON;
    ____ ENDIF ;
    __ ENDOF ;

    __ EW_YELLOW OF
    ____ IF (YELLOW_TIMER_done) THEN
    ______ cur_state <= NS_GREEN;
    ______ EW_YELLOW_light <= OFF;
    ______ EW_RED_light <= ON;
    ______ NS_RED_light <= OFF;
    ______ NS_GREEN_light <= ON;
    ______ YELLOW_TIMER_enable <= OFF;
    ______ NS_LONG_TIMER_enable <= ON;
    ______ NS_SHORT_TIMER_enable <= ON;
    ____ ENDIF ;
    __ ENDOF ;
    ENDCASE ;

    This all starts with a coherent description of the functions of the state machine, then a diagram. Once you have the diagram, it's a very simple process to turn it into the above coding style.

    In an HDL, the above code would be in a process that runs when any of the specified inputs changes. Essentially, it's a data flow design. But I don't know how you would trigger that in something like C. I suppose you have interrupts from changes of
    the inputs, as well as timer interrupts. Each of these are events that can trigger the above code to run.

    --

    Rick C.

    -- Get 1,000 miles of free Supercharging
    -- Tesla referral code - https://ts.la/richard11209

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ed Prochak@21:1/5 to jmariano on Fri Mar 10 13:11:30 2023
    On Friday, March 10, 2023 at 12:51:33 PM UTC-5, jmariano wrote:
    Hello again!

    Thank you all very much for your valuable inputs! I have A LOT of reading to do!

    I'm not a professional developer, like most of you (I guess). I'm just a "gizmo" builder, serving mainly my own research and my colleagues. I don't have access to professional development tools. My background is in physics, although I took a few
    electronics courses in college. I took courses on embedded systems, usually called "microprocessor in physics" and the like, but the emphasis of these courses tended to be on the digital part and not so much on the computational aspects of the thing.
    That is why I usually get lost on the jargon of computer science and know very little of data structures and things like that.....

    My first contact with FSM was many years ago when I was finishing my PhD. I had to implement a FSM on a FPGA and, of course, I was late, so I pick an example somewhere and just implemented, without really knowing what I was doing....

    Now I would like to finally understand a little better how these things work.

    Also, the sort of things that I build, usually have some type of "controller" in it, and usually talk to a PC via serial interface. This, it seems to me, could to be well modeled by a FSM. I understand that this representation might bring additional
    issues, as it was mentioned by Don Y, but my main goal here is to go one step further from the clumsy programming that I've been doing for the past few years into a bit more formal representation of the problems.

    I also have some questions about synchronization of several FSM, but this is a subject for a feature post.

    Cheers,
    jmariano

    Hi jmariano,
    You are clearly on the right path. FSM are useful in many situations.
    With a physics background you will get it soon.
    You can consider time as an input for synchronization. Or a messaging system. If you would like help on your software design, feel free to email me.

    My career went from physics to software, which I've done for about 40 years. Enjoy and good luck.
    Ed

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ed Prochak@21:1/5 to Don Y on Fri Mar 10 14:02:48 2023
    We think a lot alike because we've both have done this a long time.

    On Friday, March 10, 2023 at 2:10:30 PM UTC-5, Don Y wrote:
    On 3/10/2023 11:10 AM, Ed Prochak wrote:
    The problem with all of these approaches is they add another "tool" to
    the development process -- and another opportunity for the developer
    (who only uses the tool for a *portion* of a project) to make mistakes
    in its application. The more capable and expressive the tool, the
    more knowledge is required of the developer to exploit its capabilities.

    While you're last statement is true, I do not think it is a valid argument against
    adding another tool to the development process. In a work (not hobby) environment, I expect good management and teams to make considered
    choices about what tools to apply. If a tool is feature rich and well documented,
    then the knowledge should be available (either in you head or in the manual)
    to make the job easier.
    You have to decide if the effort to learn (and remain "current")
    the tool offsets the advantages gained by using it. I see lots
    of developers who "almost" know how to use something. IMO, this
    is worse than *not* knowing hot to use it (or, not *having* the
    tool) because it breeds a false sense of confidence in their
    efforts.
    To your 1st sentence: AMEN. We don't really disagree here.
    To the rest: I've seen it too. Developers that write C code
    thinking they are programming in C++ is an extreme example.

    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory? Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    Yes.
    When I have done a lot of regex work I kept a quick reference card handy.
    The qualifier on that last statement is the issue. What about
    when you HAVEN'T done work with a tool for some period of time?
    Will you admit to yourself that you likely need a "refresher"?
    Or, will you stumble along and *hope* it "comes back to you"?
    Error-free?

    Personally, Yes, I admit to my own limitations.

    E.g., are multidimensional arrays stored in row or column major order?
    There are too many "little details" like this that only stay fresh
    in your mind with "frequent refreshing".

    True, but that is a different issue that the selection of the tool in the first place.

    [I write a lot of formal documentation. Yet, I'll be damned if I can remember the shortcut for "non-breaking hyphenation" -- despite using
    it dozens of times on any given document! (tomorrow, I'll look it up, again!)]

    You're preaching to the choir here Don.

    Instead (IMO), you want something that lets a developer use a technology >> without reliance on a particular tool (that may not be well-supported
    or may have latent bugs that haven't yet been tickled).

    Being tool agnostic is an ideal goal. In a practice, you must pick some specific tools to get the work done within schedule, budget, and quality constraints.

    If you want portability of design then that should be an explicit fourth constraint.
    Most projects select tools with the additional LONG TERM constraint of support throughout the life of the product or product line.
    I've made a lot of money addressing the needs of clients who banked on
    a set of tools, only to discover that they were "no longer supported"
    (e.g., doesn't run unde new version of OS, requires hardware that PCs no longer include, etc.)

    Yes a lot of projects don't follow due diligence. Hence my phrase
    earlier about "good management and teams" was used deliberately.

    You don't realize this is a legitimate design issue until you get
    bitten by it. And, at that time, the time and $$$ available are
    seldom what you'd need.

    Yes. That's true. Computer engineering has been in constant flux for
    70+years. Those that fail to learn history are condemned to repeat it.

    As such, understanding the technique is more important than finding a tool
    that may )or may not) address your needs. ("I want to write in Eiffel.
    Does your tool output Eiffel source?" Next week it may be some other
    /langue du jour/)

    Exactly why an abstracting tool that is more focused on the design is better than
    a specific tool.
    What better than a human brain?

    I was focusing on documenting the design. I've tried to leave projects
    with enough clear design so that I can be replaced.
    State machine design tools are a good example. With a graphic design tool, it is much easier to spot missing states or incorrect transitions. It can be
    clear enough that even end users can understand and point out flaws or enhancements. I don't think the particular output language is the point here.
    But you can use a pencil and paper (or drawing program) to make
    such a diagram and "see" the same missing states/incorrect transitions.

    Well, whiteboard is easier to "edit and then save via camera (&/or printer)

    The tool just automates binding the design to a particular implementaion.

    Ideally it should do both, act as a design recording medium and
    output the implementation.

    BTW, lots of your earlier comments match closely to what I would have posted.
    This one just struck me are being a little too idealistic.

    Have you looked at Harel charts? For *complex*/layered machines?
    I suspect revisiting one that you managed to *coax* together at
    the start of a project a year or two later (maintenance) would
    leave you wondering what all of the cryptic notation means.

    Haven't used Harel charts. That's part of UML and no place that
    I worked at has needed a tool set that complex. KISS
    Taking a quick look, [again!] I can see why it may not be a good choice.

    Would you admit ignorance and "play it safe" -- and expect to have
    time to refamiliarize yourself with it BEFORE trying to make
    changes? Or, would you proceed with a false set of confidence
    and *hope* you don't break anything along the way?

    If it is the design, then it better be clear enough to just read through
    and be understandable and complete.
    If it is the implementation, then I never assume I am familiar with
    the code (even it I wrote it).

    Because we all KNOW that we have more than adequate time to
    devote to "doing it right", right? :>

    On some projects more than others. But I've managed to work on a couple projects where putting in the time up front in design paid off big in the integration and release. I'm especially proud of those projects.

    [This is why I push the "idealistic" because in practice is far from it]

    At one of the last places I worked we developed a saying that
    we violently agree. 8^)

    You make great contributions here Don. Keep it up.
    Ed

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to jmariano65@gmail.com on Fri Mar 10 16:49:34 2023
    On Thu, 9 Mar 2023 14:17:14 -0800 (PST), jmariano
    <jmariano65@gmail.com> wrote:

    Does anyone know of a nice text on finite state machines and their
    software implementation on embedded systems?

    I'm looking for some theoretical background and design methodology. A
    few examples of "C" implementation would be a nice but not really
    needed. I'm not looking for a recipe or code but for a more formal >explanation on the workings of FSM.
    Thanks
    jmariano

    If you *really* want the theory, you can take a look at Hopcroft and
    Ullman's "Intro To Automata Theory, Languages And Computation".

    It's available free as an ebook from https://archive.org/details/HopcroftUllman_cinderellabook/mode/2up


    Be aware that there is little/no code in this book - it's theory only
    and you will need to figure out how to implement.

    Good luck!
    George

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Ed Prochak on Sat Mar 11 06:01:40 2023
    On 3/10/2023 3:02 PM, Ed Prochak wrote:
    We think a lot alike because we've both have done this a long time.

    Long lost brother? <raises eyebrows> Tell me, do you, too, have
    that extra thumb on your left hand?? :>

    [E.g., if you had to construct an arbitrary regex, could you do so
    with the knowledge you have committed to memory? Would a reference
    answer all of the questions you *might* have about your particular
    pattern?]

    Yes.
    When I have done a lot of regex work I kept a quick reference card handy. >> The qualifier on that last statement is the issue. What about
    when you HAVEN'T done work with a tool for some period of time?
    Will you admit to yourself that you likely need a "refresher"?
    Or, will you stumble along and *hope* it "comes back to you"?
    Error-free?

    Personally, Yes, I admit to my own limitations.

    There are two issues potentially at play, here.

    One is "vanity/pride/overestimation of your abilities".
    The other is simply not *knowing* that you don't really know what
    you need to know (or, that you know "enough" that the "rest" is
    insignificant)

    [Recall, we also have to address newcomers to the codebase
    who may be seeing these techniques for the first time and
    convince themselves that they *think* they know what it does]

    E.g., are multidimensional arrays stored in row or column major order?
    There are too many "little details" like this that only stay fresh
    in your mind with "frequent refreshing".

    True, but that is a different issue that the selection of the tool in the first place.

    I'm just commenting on "little details" that are too easy to "forget to remember" -- like the nonbreaking hyphen, below. In my defense, I
    *know* that the nonbreaking hyphen exists, why/where it should
    be used AND HOW *EASILY* I CAN REFRESH MY MEMORY (by opening the
    "shortcuts" manual). So, the fact that I keep forgetting the
    shortcut doesn't bother me.

    But, some of the FSM tools are overly cryptic in how they try
    to encode different "machine aspects" into the definition of
    the machine. How likely would a "stale" developer be to
    remember what all of those are and how they are used/invoked?
    Would he be humble/practical enough to admit his need for a
    refresher? Would he have the *time* to do so (perceived or
    otherwise)?

    Instead (IMO), you want something that lets a developer use a technology >>>> without reliance on a particular tool (that may not be well-supported
    or may have latent bugs that haven't yet been tickled).

    Being tool agnostic is an ideal goal. In a practice, you must pick some
    specific tools to get the work done within schedule, budget, and quality constraints.

    If you want portability of design then that should be an explicit fourth constraint.
    Most projects select tools with the additional LONG TERM constraint of
    support throughout the life of the product or product line.
    I've made a lot of money addressing the needs of clients who banked on
    a set of tools, only to discover that they were "no longer supported"
    (e.g., doesn't run unde new version of OS, requires hardware that PCs no
    longer include, etc.)

    Yes a lot of projects don't follow due diligence. Hence my phrase
    earlier about "good management and teams" was used deliberately.

    This is the crux of our difference. Ed, you (appear to be) optimistic
    about what you can expect from other developers and managers. I'm
    highly (HIGHLY!) jaded/cynical. I expect people to be lazy (why crack
    open a reference?) as well as over-estimate their abilities.

    I've personally seen products released with KNOWN bugs, rationalizing
    that they aren't "that important" (isn't the customer/end user the one
    who is supposed to make that decision?). Or, that they'll be fixed in an upcoming release -- that never comes (because there are other issues
    that preempt it in the queue).

    Even the simplest/easiest of tasks often get overlooked or ignored.

    I have been trying to get a copy of my medical record under HIPPA.
    This is a CIVIL RIGHT (!) I filled out the APPROVED form. HAND
    DELIVERED to the office (so no need to rely on the mail for prompt
    delivery). "It can take up to 30 days" (because a third party has to
    fulfill the request)

    Sit back and *patiently* wait. Don't pester them cuz they've already
    told you "up to 30 days". And, maybe a few more in case it got mailed
    on the 30th day!

    Day 35, time to call and see what's going on. "Press 3 for Records
    and Referrals" 10 rings. Voicemail. OK, its possible the person
    is away from their desk. Leave message, name, reason for calling,
    callback number.

    Next day, still no callback. Try again. Same result. Leave ANOTHER
    message.

    Lather, rinse, repeat for a total of 5 days!

    OK, they've obviously got a problem with their "Records and Referrals"
    person! Escalate to office manager. Don't accept their offer to "send
    me to her voicemail" as we've done that for 5 days, already, with the
    records flunky.

    Office manager will track down the records person (why isn't she
    at work? at 9:30AM? Office opens at 8!) and get back to me.

    Don't pester later that day -- give them time, you're not the only
    thing that they need to deal with.

    Or, the next.

    Two days later, call again and office manager avoids the call relying
    on someone else to give me a message: A third party "records" firm
    is processing my request (Duh, we knew that when I dropped off the
    request ~6 weeks earlier!). "When can I expect it from THEM?" Dunno.
    "Who can I call for more information?" (cuz you folks have been really
    tedious to deal with). Dunno.

    Eventually, get office manager on the phone. She's got their phone
    number and a "request ID" used to identify the request initiated on
    my behalf.

    Call third party. Automated "Your request is being processed. Call
    back in 7 days." (we're well beyond that 30 day initial limit).

    Out of curiosity, call back and talk to a human. Who repeats the
    same 7 day message. "Why is it taking so long? I requested these
    40+ days ago!"

    "We just got the request YESTERDAY."

    Ah, so now I know that the "office" never filed my request.
    They just lost it (or it was still sitting in the Records person's
    inbox).

    Long story. But, the point is, all the Records person had to do was
    pass the request on to the third party, initially. Insert in FAX machine
    (or document scanner) and press "SEND". Yet, this was beyond their
    abilities!

    And, thereafter (had they done their job originally), all they
    would have had to do to address my followups was to give me the phone
    number and identifier for me to contact the third party!

    Instead, they try to hide the fact that they didn't do their
    job (office manager didn't say, "Gee, Don, we're really sorry
    but your request got lost, somehow. We only just recently
    submitted it to the third party (AFTER YOU PESTERED US). We'll
    try to EXPEDITE it for you (so YOU don't have to deal with
    *OUR* problem)"

    [What would have happened had I been in need of a REFERRAL and the
    "Records and Referrals" person been just as negligent? Would I
    have been letting the clock run out on a potentially serious
    medical condition?]

    Back to the topic at hand...

    Ask developers why their code is buggy and they'll tell you
    it's because their *boss* doesn't give them time to do proper
    testing, or doesn't have good design practices in place, etc.
    AS IF *they* would do much better in the absence of those
    nasty, ignorant managers and corporate culture.

    Yet, look at FOSS products -- no boss, no deadlines, put
    whatever design practices *you* want in place (after all,
    it's !your! name that will be on the "product"; you won't
    be some anonymous employee/developer) and you still see
    the same /ad hoc/ practices at play.

    Or, *you* WITNESSED a particular bug while testing your code.
    Yet, you weren't able to (easily) reproduce it. So, you
    DISMISS it (as a fluke) -- despite KNOWING that it happened
    and you haven't done anything DELIBERATE to eliminate it.
    Really? What's your defense? You'll address it when
    some user encounters it? Or, you'll hope some other user
    finds and fixes it?

    <frown> No, I don't expect developers to "do the right thing"
    any more than I expect managers to put in place the right practices.
    There's always a rationalization...

    Jaded.

    As such, understanding the technique is more important than finding a tool >>>> that may )or may not) address your needs. ("I want to write in Eiffel. >>>> Does your tool output Eiffel source?" Next week it may be some other
    /langue du jour/)

    Exactly why an abstracting tool that is more focused on the design is better than
    a specific tool.
    What better than a human brain?

    I was focusing on documenting the design. I've tried to leave projects
    with enough clear design so that I can be replaced.

    My arguments have been towards "tools that do this code generation" (pozz/roland's upthread comments). To do so, they have to have more information encoded in the representation.

    A classic state diagram is conceptually simple. You don't have special
    symbols to reference timed transitions, history, nested machines, etc.
    Yet, even a small number of states can quickly have too many transitions
    to easily represent on a sheet of paper.

    This is a small (hardware) FSM. Poor quality because it was *hand*
    drawn (straight edge, lead holder and lettering guide) on a *D*-size
    sheet of paper, reduced to B size and then scanned at 1200dpi (in an
    attempt to preserve as much detail as possible)!

    <https://mega.nz/file/A3x03aAA#YNsJhdikiucjU6aKGWKL2eTu4D0i95sqjcLuzIhz7ys>

    8 state variables (Moore type so they also represent the outputs). About
    35 states. And, it is highly regular (as evidenced by the symmetry in
    the presentation. Imagine an embedded system (software) that has all
    sorts of "exceptions" to take into consideration (transtion vectors crisscrossing willy-nilly)

    All this machine does is act as mediator between an internal FIFO
    (controlled by another FSM) and a "swappable hardware interface".
    It allows that interface to present dots (foreground/background),
    lines (essentially "line feeds") and pages to a marking engine.
    It prioritizes multiple concurrent requests and acknowledges each
    (interlocking REQ-ACK handshake). The "work" it does is sorting out
    how to accumulate dots to deliver to the FIFO as packed bytes,
    pad out lines that have not specified enough dots to fill the
    width of the line, etc. I.e., it is *trivial*. Yet, a visual mess.

    The tool just automates binding the design to a particular implementaion.

    Ideally it should do both, act as a design recording medium and
    output the implementation.

    But, again, if what you're trying to codify (in the drawing) has
    too many subtleties, how likely will you be to draw it correctly?
    How often will you "test your knowledge" just by seeing if it
    (the code) *appears* to do what you want?

    [Have a look at all of the subtlety in Harel state charts. They
    try to encode *everything* in the drawing -- which is what you'd
    need to do if you were going to generate code FROM that drawing.
    I contend that someone using them day in, day out, *could* likely
    be very effective with them. But, someone just using them for
    part of a project would perpetually be uncertain of the actions
    they're taking wrt them. Like trying to create an arbitrary
    regex after months of not using them]

    Documents should *enhance* your understanding of a product
    (or its behavior). They shouldn't become challenges in and of
    themselves (because they try to be equivalences for the actual code!)

    Each "node" in my current design operates in several "major states".
    It can be:
    - powered down (by the system)
    - powering up (at the behest of the system, running POST, acquiring OS image)
    - running diagnostics (taken out of service due to a fault)
    - powered up but "cold" (ready to receive application code, known to be
    in a reliable hardware state to deliver its potential functionality)
    - powered up and actively providing services
    - powered up but only serving compute resources (field is powered off)
    - powering down (shedding load to other nodes at the behest of the system)
    - faulted (failure detected so trying to recover)
    etc.

    It's really just a handful of different *major* states. Yet, the state
    diagram is a mess, just indicating how a node can move from one of
    these states to another (ignoring all of the "details" involved). And,
    it ignores the many states *within* a major state (to manage complexity)

    If you think about *applications*, in detail, you quickly discover that there are lots of states that need to be visible -- IF YOU WANT TO MODEL THE ENTIRE APPLICATION as a FSM).

    Instead, use state diagrams to explain portions of the design in
    more intuitive/visual ways -- without requiring them to be "complete
    enough" to generate code (via some other tool). Let the developer look elsewhere for more detail -- expressed in a medium that is better
    suited to THAT task.

    BTW, lots of your earlier comments match closely to what I would have posted.
    This one just struck me are being a little too idealistic.

    Have you looked at Harel charts? For *complex*/layered machines?
    I suspect revisiting one that you managed to *coax* together at
    the start of a project a year or two later (maintenance) would
    leave you wondering what all of the cryptic notation means.

    Haven't used Harel charts. That's part of UML and no place that
    I worked at has needed a tool set that complex. KISS
    Taking a quick look, [again!] I can see why it may not be a good choice.

    But that shouldn't rule out using state diagrams *or* state
    machines. Harel just seems to think "if a little is good, a lot
    will be better!"

    I've implemented (software) state machines in probably a few dozen
    *different* ways -- each an "experiment" to see if some tweek of an
    earlier approach can give me better "app coverage" (without jeopardizing
    the project's design). My conclusion: there is no *best* way. So,
    adopting *an* approach (dictated by a tool) seems like a straight-jacket.

    The important thing is to have a visible structure to the design
    that is reasonably easy to grasp -- without wading through pages
    of case/switch statements with interspersed code fragments.

    Would you admit ignorance and "play it safe" -- and expect to have
    time to refamiliarize yourself with it BEFORE trying to make
    changes? Or, would you proceed with a false set of confidence
    and *hope* you don't break anything along the way?

    If it is the design, then it better be clear enough to just read through
    and be understandable and complete.

    Does it really need to be complete for you to grok intent?
    Do I really care how the code detects a power failure to
    understand how it *reacts* to one? Use good abstractions
    and the next guy won't *have* to understand ALL of it.

    If it is the implementation, then I never assume I am familiar with
    the code (even it I wrote it).

    Wise man! :> Also, a testament to writing what needs to
    be written, instead of trying to be clever!

    I had to modify some of my baking Rxs do to egg shortages.
    Being reasonably good with arithmetic, I can scale recipes
    in my head. So, just jot down the actual quantities that
    I used (after scaling).

    Now, going back to clean up the little scraps of paper that
    I used (is this a universal truth? I always see folks with
    Rxs on oddball slips of paper -- the back of cash register
    receipts, envelopes, etc. I think the only folks who use
    those nice printed index cards are folks who don't bake! :<),
    I find some quantities that obviously aren't in the correct
    scaled proportions evident in the *other* quantities.

    As it's unlikely that I "figured wrong", there must have been
    a reason for my instituting that change... yet I've neglected
    to commit it to paper so I'm now at risk of having to make a
    similar change, in the future, and not realizing it until after
    the fact! :<

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Don Y on Sat Mar 11 06:17:13 2023
    On 3/11/2023 6:01 AM, Don Y wrote:
    If you think about *applications*, in detail, you quickly discover that there are lots of states that need to be visible -- IF YOU WANT TO MODEL THE ENTIRE APPLICATION as a FSM).

    Instead, use state diagrams to explain portions of the design in
    more intuitive/visual ways -- without requiring them to be "complete
    enough" to generate code (via some other tool).  Let the developer look elsewhere for more detail -- expressed in a medium that is better
    suited to THAT task.

    Old-school analogy: you'd no longer write a (detailed) flowchart
    to document a piece of code (before or after writing it). But, would
    likely sketch the "major flow" -- even if only on the back of a
    napkin -- to act as general guidance.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pozz@21:1/5 to All on Mon Mar 13 16:35:06 2023
    Il 10/03/2023 16:20, Rick C ha scritto:
    On Friday, March 10, 2023 at 6:48:13 AM UTC-5, Robert Roland wrote:
    On Fri, 10 Mar 2023 09:54:23 +0100, pozz <pozz...@gmail.com> wrote:

    There will be a time when the programmer would simply draw one or more
    state-machines and click a button to generate the full working code in
    whatever language.
    When I went to school, 30 or so years ago, we did have such a program.
    I am not able to remember its name, though.

    The problem these have, like many graphic oriented approaches, is continuing support and version control of the source. I've never worked with a state machine that was so complex it required anything other than a diagram drawn up in your favorite word
    processor drawing package. Typically, FSM can be decomposed into multiple distinct FSM that are easier to understand, and typically relate to the problem better.

    A FSM with 10 states is easy to code. A FSM with 100 states is probably several FSMs, mistakenly combined into one.

    I think the complexity of a FSM is not only related to the number if
    states, but also to the transitions/inputs.

    It's much simpler to detect errors on a diagram instead on a cryptic
    list of switch/case instructions. The great advantage of a diagram is
    that it can be read by non-developers: customers, sales man, project
    managers and so on.

    UML diagrams are there exactly for these reasons.

    Now imagine a tool that takes as input the diagram and spits out a fsm.c without errors.

    I know most of FSMs are simple, but most of the time you refuse to solve
    a problem with a FSM just because it could be too complex to convert in
    code. However, if you had this type of tool, you would consider FSMs for
    much more problems.

    For example, a simple calculator can be modeled as a FSM: the
    transitions are keystrokes. However it isn't a simple FSM, because there
    are many subtle details that must be addressed. It is somewhat simple to
    make a diagram and solve some problems with arcs, arrows and rectangles,
    but it's much more complex to make the same things on a C code.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From StateMachineCOM@21:1/5 to All on Mon Mar 13 08:55:29 2023
    Now imagine a tool that takes as input the diagram
    and spits out a fsm.c

    Indeed. Please check out the freeware QM modeling tool:
    - https://www.state-machine.com/qm

    Automatic Code Generation video:
    - https://youtu.be/FHV5vZyECOA

    QM is an example of a modern, *lightweight* modeling tool. Older, "high-ceremony" tools have been available since the '90, but they couldn't pull their own weight and didn't catch on. However, things changed since the '90s...

    For example, a simple calculator can be modeled as a FSM: the
    transitions are keystrokes. However it isn't a simple FSM...

    Interesting that you mention the calculator problem. (I've used it in my "Practical Statecharts" book published back in 2022.) I've also used it in my recent video "State machines as "spaghetti" reducers:

    - https://youtu.be/fLXxNe4YeJ4

    It turns out that even the "simple" 4-operation calculator is complex enough to be almost impossible to get right with traditional "improvised" state management. A state machine, on the other hand, is quite manageable.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to pozz on Mon Mar 13 09:29:40 2023
    On 3/13/2023 8:35 AM, pozz wrote:
    I think the complexity of a FSM is not only related to the number if states, but also to the transitions/inputs.

    Of course. A 0..99 counter can have oodles of states... but the interactions between them are trivial to the point of being boring.

    Interconnectedness is the source of *all* complexity. E.g., the more
    modules your code interacts with, the more complex the code is likely to be!

    It's much simpler to detect errors on a diagram instead on a cryptic list of switch/case instructions.

    That depends on the problem and how expressive (and intuitive!) the
    drawing.

    The great advantage of a diagram is that it can be
    read by non-developers: customers, sales man, project managers and so on.

    See above.

    UML diagrams are there exactly for these reasons.

    Now imagine a tool that takes as input the diagram and spits out a fsm.c without errors.

    For what *classes* of state machines?

    I know most of FSMs are simple, but most of the time you refuse to solve a problem with a FSM just because it could be too complex to convert in code. However, if you had this type of tool, you would consider FSMs for much more problems.

    You're already building an FSM when you solve such a problem. Th eissue is
    HOW you build it. If /ad hoc/ then it's more likely to contain errors
    and be harder for folks to understand -- without digging deep.

    For example, a simple calculator can be modeled as a FSM: the transitions are keystrokes. However it isn't a simple FSM, because there are many subtle details that must be addressed. It is somewhat simple to make a diagram and solve some problems with arcs, arrows and rectangles, but it's much more complex to make the same things on a C code.

    A calculator is a toy example.

    Imagine, instead, if that calculator (keys + display) also had to
    indicate when a key was *stuck*, when the batteries were low
    (without a dedicated "low battery" indicator), the current time
    of day (and date), implement an "egg timer" functionality as well
    as a traditional "alarm", etc.

    A calculator is *just* a calculator. Few embedded systems have such
    limited -- and "regular" -- functionality.

    Imagine being in the middle of a calculation and the display is
    commandeered by the alarm function signalling the programmed time
    has been reached. The value you had been typing is overwritten
    AND your next keystroke is expected (but not guaranteed) to be
    an interaction with the "alarm clock" -- acknowledge the alarm,
    request an additional 10 minutes, leave it active (but not
    monopolizing the display) for later attention, etc.

    This condition can persist indefinitely (unless the design
    times it out). So, what happens when the battery fails
    some time later? Does that message overlay the alarm
    message? How do you interact with THAT? And, when you've
    "cleared" it, does the alarm display reappear? Do you
    even *remember* the calculation that you were making when
    this started?

    [And let's not get into the possibilities for races because
    you hadn't considered how one "process/interaction" could be interrupted/preempted by another!]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Gerhard Hoffmann@21:1/5 to All on Mon Mar 13 21:20:25 2023
    Am 10.03.23 um 18:51 schrieb jmariano:

    My first contact with FSM was many years ago when I was finishing my PhD. I had to implement a FSM on a FPGA and, of course, I was late, so I pick an example somewhere and just implemented, without really knowing what I was doing....

    Now I would like to finally understand a little better how these things work.

    Also, the sort of things that I build, usually have some type of "controller" in it, and usually talk to a PC via serial interface. This, it seems to me, could to be well modeled by a FSM. I understand that this representation might bring additional
    issues, as it was mentioned by Don Y, but my main goal here is to go one step further from the clumsy programming that I've been doing for the past few years into a bit more formal representation of the problems.

    I learned state machines using the PLD compiler "CUPL" on
    a VAX11/750. That had a nice syntax for Mealy and Moore
    state machines and once I had understood that, I could also
    do it in PALASM or VHDL.

    Another useful tool is yacc from Unix or bison on Linux.
    It reads a grammar and builds a state machine from it.
    The state machine is later a blob of C. It reads a series of
    symbols from its input much like your serial device and hopps
    through the state machine if the string of symbols is legal
    given the grammar. If a legal grammar rule is recognized,
    you can trigger the execution of some C code.

    Grammar:

    expression = number: { $$ = $1; )
    | expression = sym_variable: { $$ = $1; )
    | expression = Number '+' number : { $$ = $1 + $3; }
    | expression = epression '*' expression: { $$ = $1 * $3; }
    | expression = epression '/' expression: { $$ = $1 / $3; }
    | expression = '(' expression and so on

    assignment = sym_variable ":=" expression: { $$ = $3; )

    My syntax is wrong, but you get the principle. The stuff
    in curly braces is the executed C code when a rule is
    discovered, the $$ thingies are replaced by the relevant variables. Yacc
    tries to find the most general rules possible.
    That makes sure that y = a + b + c * sin(333); is recognized.

    Usually you will insert a lexical scanner in the input, like
    lex, so that the state machine does not grow beyond a reasonable
    size. That would filter out things like "begin", "end",
    "integer", ":=" and so on. lex() would return integer constants
    like SY_BEGIN, VARIABLE_NAME, '(' and so on.

    Yacc stands for "Yet Another Compiler Compiler", but it's
    original job was to create Weinberger arrays, a LSI structure
    not unlike a PAL.

    Cheers,
    Gerhard

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to Gerhard Hoffmann on Mon Mar 13 15:41:32 2023
    On 3/13/2023 1:20 PM, Gerhard Hoffmann wrote:
    I learned state machines using the PLD compiler "CUPL" on
    a VAX11/750. That had a nice syntax for Mealy and Moore
    state machines and once I had understood that, I could also
    do it in PALASM or VHDL.

    CUPL, PALASM, PLDshell, ABEL, etc.

    Another useful tool is yacc from Unix or bison on Linux.
    It reads a grammar and builds a state machine from it.
    The state machine is later a blob of C. It reads a series of
    symbols from its input much like your serial device and hopps
    through the state machine if the string of symbols is legal
    given the grammar. If a legal grammar rule is recognized,
    you can trigger the execution of some C code.

    Yes, but tedious for things like event driven code (where the symbols
    in the alphabet are events).

    Grammar:

    expression = number:                       { $$ = $1;    )
    | expression = sym_variable:               { $$ = $1;    )
    | expression = Number '+' number :         { $$ = $1 + $3; }
    | expression = epression '*' expression:   { $$ = $1 * $3; }
    | expression = epression '/' expression:   { $$ = $1 / $3; }
    | expression = '(' expression and so on

    assignment = sym_variable ":=" expression:  { $$ = $3;    )

    My syntax is wrong, but you get the principle. The stuff
    in curly braces is the executed C code when a rule is
    discovered, the $$ thingies are replaced by the relevant variables. Yacc tries
    to find the most general rules possible.
    That makes sure that y = a + b + c * sin(333); is recognized.

    Usually you will insert a lexical scanner in the input, like
    lex, so that the state machine does not grow beyond a reasonable
    size. That would filter out things like "begin", "end",
    "integer", ":=" and so on. lex() would return integer constants
    like SY_BEGIN, VARIABLE_NAME, '(' and so on.

    lex(1) most often used to aggregate things like <digit>s into <number>
    or <character>s into <token>/<reserved_word> without unduly burdening
    the grammar with individual rules for each token. No real "actions"
    done inside those tokens.

    lex/yacc are more applicable to parsing strings of characters
    as you'd encounter in a (programming) language.

    AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc.

    Yacc stands for "Yet Another Compiler Compiler", but it's
    original job was to create Weinberger arrays, a LSI structure
    not unlike a PAL.

    Cheers,
    Gerhard



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Mon Mar 13 23:07:02 2023
    On Mon, 13 Mar 2023 15:41:32 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    lex/yacc are more applicable to parsing strings of characters
    as you'd encounter in a (programming) language.

    AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc.

    yacc and bison will remove unreachable PDA states. Unreachable states
    in the parser typically are not deliberate but rather result from
    ambiguities in the grammar. lex, being LALR(1), does not tolerate
    much ambiguity - however bison can generate more powerful LR(1) and
    GLR(1) parsers, and these are far more likely to have unreachable
    states accidentally generated.


    Similarly, lex will remove unreachable DFA states. Again, these
    typically are not deliberate, but rather result from lex combining the
    various input regex into a single DFA having multiple entry points and
    multiple accepting states.


    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in
    the same action. And finally, flex can [sometimes] discover
    "meta-equivalence" classes: commonly expected sequences of characters
    and/or other equivalence classes.

    All of these can result in a smaller and/or more efficient recognizer
    than is possible with lex.



    yacc and lex are ancient and haven't been maintained for decades. They
    have limitations that will never be addressed, and bugs that will
    never be fixed. If you really need backward compatibility with them,
    it is available using bison and flex ... and if you don't need bacward compatibility, bison and flex are much more powerful and modern tools
    for use with new projects.
    [There are other modern alternatives to yacc and lex also, but
    discussion of them is beyond the scope of this missive.]

    George

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Mon Mar 13 22:59:25 2023
    On 3/13/2023 8:07 PM, George Neuner wrote:
    On Mon, 13 Mar 2023 15:41:32 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    lex/yacc are more applicable to parsing strings of characters
    as you'd encounter in a (programming) language.

    AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc.

    yacc and bison will remove unreachable PDA states. Unreachable states
    in the parser typically are not deliberate but rather result from
    ambiguities in the grammar. lex, being LALR(1), does not tolerate
    much ambiguity - however bison can generate more powerful LR(1) and
    GLR(1) parsers, and these are far more likely to have unreachable
    states accidentally generated.

    Similarly, lex will remove unreachable DFA states. Again, these
    typically are not deliberate, but rather result from lex combining the various input regex into a single DFA having multiple entry points and multiple accepting states.

    But, the FSM parallel would be an "orphan" state that would (*likely*)
    be readily apparent in a state diagram. "You can't get there from here".

    In hardware designs, it is not uncommon to have multiple equivalent
    states that aren't easily recognized as such (because you tend to
    ignore "don't cares" -- which are ripe for collapsing equivalents).

    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in
    the same action. And finally, flex can [sometimes] discover "meta-equivalence" classes: commonly expected sequences of characters
    and/or other equivalence classes.

    But, those are mapping equivalent *inputs* together, not equivalent
    *states*. I.e., treating "BEGIN" and "begin" as equivalent.

    Would it recognize a common sequence of state transitions
    that occurs in two different places in the grammar? E.g.,
    specifying the syntax for a numeric quantity in two different
    places only to realize that it's actually the same part of
    the grammar expressed as two different instances?

    <number> ::= <digit><digits>

    <value> ::= <digit><digits>

    <expr> ::= <number> <op> <number> | <value> <op> <value>

    (silly example; but the inputs in each case are the same)

    All of these can result in a smaller and/or more efficient recognizer
    than is possible with lex.

    yacc and lex are ancient and haven't been maintained for decades. They
    have limitations that will never be addressed, and bugs that will
    never be fixed. If you really need backward compatibility with them,
    it is available using bison and flex ... and if you don't need bacward compatibility, bison and flex are much more powerful and modern tools
    for use with new projects.
    [There are other modern alternatives to yacc and lex also, but
    discussion of them is beyond the scope of this missive.]

    Now, to the question at hand (or, a version thereof).

    It's relatively common to design an algorithm or a hardware machine
    as a "state transition diagram" and then reduce to an implementation.
    (The question, posed here, is how much this can be automated and
    the benefits that might acrue).

    But, IME, DFA for grammars are most usually expressed in (e)bnf
    and *later* rendered into graphical form (e.g., railroad dwgs).
    The graphical form being a consequence of the specification,
    not the *driver* of the specification. And, the graphical
    form often *omitting* detail (i.e., how each rule in the grammar
    is acted upon).

    Said another way, do you see folks designing grammars graphically
    and relying on tools to convert these "expressions" to more
    concrete forms for parser generators? (this being the analog of
    how graphical tools are being suggested for application to FSM)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pozz@21:1/5 to All on Tue Mar 14 15:44:05 2023
    Il 13/03/2023 16:55, StateMachineCOM ha scritto:
    Now imagine a tool that takes as input the diagram
    and spits out a fsm.c

    Indeed. Please check out the freeware QM modeling tool:
    - https://www.state-machine.com/qm

    Automatic Code Generation video:
    - https://youtu.be/FHV5vZyECOA

    QM is an example of a modern, *lightweight* modeling tool. Older, "high-ceremony" tools have been available since the '90, but they couldn't pull their own weight and didn't catch on. However, things changed since the '90s...

    Yes, I know this tool and I like its philosopy. However I understood I
    can't use the generated code in closed source projects without a
    commercial license.


    For example, a simple calculator can be modeled as a FSM: the
    transitions are keystrokes. However it isn't a simple FSM...

    Interesting that you mention the calculator problem. (I've used it in my "Practical Statecharts" book published back in 2022.) I've also used it in my recent video "State machines as "spaghetti" reducers:

    - https://youtu.be/fLXxNe4YeJ4

    I read your book ;-)


    It turns out that even the "simple" 4-operation calculator is complex enough to be almost impossible to get right with traditional "improvised" state management. A state machine, on the other hand, is quite manageable.

    *Hierarchical* state-machine is fundamental here to reduce complexity.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pozz@21:1/5 to All on Tue Mar 14 15:54:35 2023
    Il 13/03/2023 17:29, Don Y ha scritto:
    On 3/13/2023 8:35 AM, pozz wrote:
    I think the complexity of a FSM is not only related to the number if
    states, but also to the transitions/inputs.

    Of course.  A 0..99 counter can have oodles of states... but the interactions
    between them are trivial to the point of being boring.

    Interconnectedness is the source of *all* complexity.  E.g., the more modules your code interacts with, the more complex the code is likely to
    be!

    It's much simpler to detect errors on a diagram instead on a cryptic
    list of switch/case instructions.

    That depends on the problem and how expressive (and intuitive!) the
    drawing.

    A good diagram is always much more expressive than a good code, for
    developers and for non-developers.


    The great advantage of a diagram is that it can be read by
    non-developers: customers, sales man, project managers and so on.

    See above.

    UML diagrams are there exactly for these reasons.

    Now imagine a tool that takes as input the diagram and spits out a
    fsm.c without errors.

    For what *classes* of state machines?

    Hierarchical state-machines (UML state-machines) are fully qualified
    monsters.


    I know most of FSMs are simple, but most of the time you refuse to
    solve a problem with a FSM just because it could be too complex to
    convert in code. However, if you had this type of tool, you would
    consider FSMs for much more problems.

    You're already building an FSM when you solve such a problem.  Th eissue is HOW you build it.  If /ad hoc/ then it's more likely to contain errors
    and be harder for folks to understand -- without digging deep.

    This is the reason why a fsm generation tool could help. You don't need
    to build with /ad hoc/ approach.


    For example, a simple calculator can be modeled as a FSM: the
    transitions are keystrokes. However it isn't a simple FSM, because
    there are many subtle details that must be addressed. It is somewhat
    simple to make a diagram and solve some problems with arcs, arrows and
    rectangles, but it's much more complex to make the same things on a C
    code.

    A calculator is a toy example.

    Imagine, instead, if that calculator (keys + display) also had to
    indicate when a key was *stuck*, when the batteries were low
    (without a dedicated "low battery" indicator), the current time
    of day (and date), implement an "egg timer" functionality as well
    as a traditional "alarm", etc.

    A calculator is *just* a calculator.  Few embedded systems have such
    limited -- and "regular" -- functionality.

    Imagine being in the middle of a calculation and the display is
    commandeered by the alarm function signalling the programmed time
    has been reached.  The value you had been typing is overwritten
    AND your next keystroke is expected (but not guaranteed) to be
    an interaction with the "alarm clock" -- acknowledge the alarm,
    request an additional 10 minutes, leave it active (but not
    monopolizing the display) for later attention, etc.

    This condition can persist indefinitely (unless the design
    times it out).  So, what happens when the battery fails
    some time later?  Does that message overlay the alarm
    message?  How do you interact with THAT?  And, when you've
    "cleared" it, does the alarm display reappear?  Do you
    even *remember* the calculation that you were making when
    this started?

    [And let's not get into the possibilities for races because
    you hadn't considered how one "process/interaction" could be interrupted/preempted by another!]

    I didn't get your point of these all. There are simple applications and
    complex applications, I don't think you need to convince me.

    Anyway even a simple application such as a standard calculator can be
    complex enough to be implemented as a flat state-machine without any tool.

    However the complexity can be managed and reduced on a diagram of a UML/hierarchical state-machine. After that, click on a build button and
    you error-free code is done.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to pozz on Tue Mar 14 08:39:21 2023
    On 3/14/2023 7:54 AM, pozz wrote:
    Il 13/03/2023 17:29, Don Y ha scritto:
    On 3/13/2023 8:35 AM, pozz wrote:
    I think the complexity of a FSM is not only related to the number if states,
    but also to the transitions/inputs.

    Of course.  A 0..99 counter can have oodles of states... but the interactions
    between them are trivial to the point of being boring.

    Interconnectedness is the source of *all* complexity.  E.g., the more
    modules your code interacts with, the more complex the code is likely to be! >>
    It's much simpler to detect errors on a diagram instead on a cryptic list of
    switch/case instructions.

    That depends on the problem and how expressive (and intuitive!) the
    drawing.

    A good diagram is always much more expressive than a good code, for developers
    and for non-developers.

    That depends on whether the diagram can express the issues that need to be expressed, *concisely*.

    nest_state := current_state + 1

    sure is a lot more descriptive than wading through 99 discrete states
    that all *seem* to say the same thing (but you must VERIFY to be sure!)

    The great advantage of a diagram is that it can be read by non-developers: >>> customers, sales man, project managers and so on.

    See above.

    UML diagrams are there exactly for these reasons.

    Now imagine a tool that takes as input the diagram and spits out a fsm.c >>> without errors.

    For what *classes* of state machines?

    Hierarchical state-machines (UML state-machines) are fully qualified monsters.

    Your goal, with *documentation* (vs specification) is to educate quickly
    and accurately. If "I" have to understand nuances of a presentation
    before the *real* meaning is apparent, then "I" will likely miss some
    detail and likely not know it (for some group of "I").

    E.g., in Limbo, there are two ways to make an assignment:

    foo := 2
    foo = 2

    Is the latter a typographical error? (No, the former instantiates and
    types the variable in addition to making the assignment; the latter simply
    does the assignment)

    I know most of FSMs are simple, but most of the time you refuse to solve a >>> problem with a FSM just because it could be too complex to convert in code. >>> However, if you had this type of tool, you would consider FSMs for much more
    problems.

    You're already building an FSM when you solve such a problem.  Th eissue is >> HOW you build it.  If /ad hoc/ then it's more likely to contain errors
    and be harder for folks to understand -- without digging deep.

    This is the reason why a fsm generation tool could help. You don't need to build with /ad hoc/ approach.

    But if the tool doesn't build the *entire* state machine portion of the code, then what good is it? If it just generates a skeleton and relies on the developer to "flesh it out", then it's just a labor saver and still leaves
    the application vulnerable to design omissions.

    For example, a simple calculator can be modeled as a FSM: the transitions >>> are keystrokes. However it isn't a simple FSM, because there are many subtle
    details that must be addressed. It is somewhat simple to make a diagram and >>> solve some problems with arcs, arrows and rectangles, but it's much more >>> complex to make the same things on a C code.

    A calculator is a toy example.

    Imagine, instead, if that calculator (keys + display) also had to
    indicate when a key was *stuck*, when the batteries were low
    (without a dedicated "low battery" indicator), the current time
    of day (and date), implement an "egg timer" functionality as well
    as a traditional "alarm", etc.

    A calculator is *just* a calculator.  Few embedded systems have such
    limited -- and "regular" -- functionality.

    Imagine being in the middle of a calculation and the display is
    commandeered by the alarm function signalling the programmed time
    has been reached.  The value you had been typing is overwritten
    AND your next keystroke is expected (but not guaranteed) to be
    an interaction with the "alarm clock" -- acknowledge the alarm,
    request an additional 10 minutes, leave it active (but not
    monopolizing the display) for later attention, etc.

    This condition can persist indefinitely (unless the design
    times it out).  So, what happens when the battery fails
    some time later?  Does that message overlay the alarm
    message?  How do you interact with THAT?  And, when you've
    "cleared" it, does the alarm display reappear?  Do you
    even *remember* the calculation that you were making when
    this started?

    [And let's not get into the possibilities for races because
    you hadn't considered how one "process/interaction" could be
    interrupted/preempted by another!]

    I didn't get your point of these all. There are simple applications and complex
    applications, I don't think you need to convince me.

    The point is that most (embedded) applications are much more substantial than
    a limited domain calculator.

    I drew parallels in the calculator example to the oven example I posted elsewhere. It *appears* to be a simple application:
    - press big button to wake up display
    - turn to select cooking mode
    - press big button to make that choice
    - turn to select cooking temperature
    - press button to make that choice
    - turn button to select next action (cook, specify time, etc.)
    - perform that step
    Repeat for the second oven.

    Ah, but, while you are specifying the temperature for the second
    oven, the cook timer for the first oven may expire (because you started
    to specify the second oven's temperature and then dashed off to remove
    the toast from the toaster).

    Now what? Do you reply to the query (from the first oven) asking if
    you want to shut the oven off or leave it on? Or, do you continue
    trying to specify the temperature for the second oven -- which is your
    memory of your most recent interaction with the oven?

    The calculator is a closed box. Nothing interacts with it other than
    the user. It can wait forever for the user to perform the next
    action (keypress) without fear of having its resources re-assigned
    to some other activity.

    If, in the example I posted, the calculator had to indicate battery
    failures, expired timers, etc. then it's considerably more involved
    in its design. This needs to be expressible in the state machine in
    a way that makes the correctness (or not) of the design apparent
    to the designer.

    [My oven fails this test! So, whatever tools the multi-BILLION dollar corporation that designed it used were inadequate for the task.]

    Anyway even a simple application such as a standard calculator can be complex enough to be implemented as a flat state-machine without any tool.

    However the complexity can be managed and reduced on a diagram of a UML/hierarchical state-machine. After that, click on a build button and you error-free code is done.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Niklas Holsti@21:1/5 to pozz on Tue Mar 14 18:49:26 2023
    On 2023-03-14 16:54, pozz wrote:
    Il 13/03/2023 17:29, Don Y ha scritto:
    On 3/13/2023 8:35 AM, pozz wrote:
    I think the complexity of a FSM is not only related to the number if
    states, but also to the transitions/inputs.

    Of course.  A 0..99 counter can have oodles of states... but the
    interactions
    between them are trivial to the point of being boring.

    Interconnectedness is the source of *all* complexity.  E.g., the more
    modules your code interacts with, the more complex the code is likely
    to be!

    It's much simpler to detect errors on a diagram instead on a cryptic
    list of switch/case instructions.

    That depends on the problem and how expressive (and intuitive!) the
    drawing.

    A good diagram is always much more expressive than a good code, for developers and for non-developers.


    In my experience, diagrams that describe all the details of the code, as
    would be required for generating the code from the diagram, are usually
    much too complex to comprehend easily ("visually"). They tend to be
    mazes where one can perhaps trace out some significant paths with a
    careful finger, unless too many lines cross at one point.

    To get a good, visually graspable diagram, IME one must almost always
    simplify and elide details. And then such diagrams are very good entry
    points into the code, if one has to read the code to get a complete understanding.

    I remember one case where the SW for the central computer of a satellite
    was generated from state-and-message diagrams by an automatic
    "model-based design" tool. In graphical form, the diagrams covered
    numerous A4 pages and each page had several cryptically labelled
    inter-page links for messages coming from other pages and going to other
    pages. It was very difficult to get any kind of overall understanding of
    the SW.

    I admit that there are some domains -- for example, servo-control
    systems -- where it is possible to generate significant amounts of code
    from readable diagrams, eg. SIMULINK diagrams. But I don't think it
    works well for most code in embedded systems.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Tue Mar 14 21:29:17 2023
    On Mon, 13 Mar 2023 22:59:25 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/13/2023 8:07 PM, George Neuner wrote:
    On Mon, 13 Mar 2023 15:41:32 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    lex/yacc are more applicable to parsing strings of characters
    as you'd encounter in a (programming) language.

    AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc.

    yacc and bison will remove unreachable PDA states. Unreachable states
    in the parser typically are not deliberate but rather result from
    ambiguities in the grammar. lex, being LALR(1), does not tolerate
    much ambiguity - however bison can generate more powerful LR(1) and
    GLR(1) parsers, and these are far more likely to have unreachable
    states accidentally generated.

    Similarly, lex will remove unreachable DFA states. Again, these
    typically are not deliberate, but rather result from lex combining the
    various input regex into a single DFA having multiple entry points and
    multiple accepting states.

    But, the FSM parallel would be an "orphan" state that would (*likely*)
    be readily apparent in a state diagram. "You can't get there from here".

    In hardware designs, it is not uncommon to have multiple equivalent
    states that aren't easily recognized as such (because you tend to
    ignore "don't cares" -- which are ripe for collapsing equivalents).

    When you merge two FSM you often get redundant "don't care" nodes, but
    you also can get nodes which either are impossible to enter [dead
    code], or impossible to leave [halt], because there are no legal
    transitions that will permit it. Joining FSM involves identifying and
    pruning both types of nodes.


    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in
    the same action. And finally, flex can [sometimes] discover
    "meta-equivalence" classes: commonly expected sequences of characters
    and/or other equivalence classes.

    But, those are mapping equivalent *inputs* together, not equivalent
    *states*. I.e., treating "BEGIN" and "begin" as equivalent.

    No. Consider the case of just recognizing a decimal digit: compare
    the graph using the alternation: (0|1|2|3|4|5|6|7|8|9), vs the graph
    using the class [:digit:].

    Using the OR alternation, including start you have 11 nodes. Start has
    10 transitions exiting, and each digit node has a single transition
    entering.

    Using the digit class, you have 2 nodes, with 10 transitions that all
    get you from start to the digit class node.



    Obviously this is simplistic, because the members of the character
    class form a subgraph which itself has to be recognized. The
    important point here is that the subgraph as a whole can represent a
    /single/ node in a much more complex graph - its constituent
    characters need not be repeated in the complex graph. More on this
    below.

    A complex DFA that combines many different regex may present other opportunities to recognize given (possibly arbitrary) sets of
    characters - opportunites that may not be apparent from looking at the constituent regex.


    Would it recognize a common sequence of state transitions
    that occurs in two different places in the grammar? E.g.,
    specifying the syntax for a numeric quantity in two different
    places only to realize that it's actually the same part of
    the grammar expressed as two different instances?

    When given the option to find equivalence classes, flex can identify
    sets of characters that are used repeatedly. Those characters are
    gathered into an "equivalence" that then can be a node in the DFA
    instead of redundantly repeating individual characters.

    Remember DFA are deterministic - a node can't take different actions
    depending on which of multiple transitions entered (or left) it ... so
    if you want the same character to be recognized in a different context
    (leading to a different action), you must repeat it in a different
    node.

    This is where being able to identify, essentially arbitrary, sets of
    character and coalesce them into a recognizer "class" is useful. If a
    given set of N(>1) characters is used M times in the graph, then by
    coalescing them you remove M(N-1) nodes from your graph. The number
    of /transitions/ in the graph remains the same, but recall that it is
    the /nodes/ that consume space in the lexer tables.



    <number> ::= <digit><digits>

    <value> ::= <digit><digits>

    <expr> ::= <number> <op> <number> | <value> <op> <value>

    (silly example; but the inputs in each case are the same)

    You're mixing abstraction levels here: <digit>, <digits>, <number>,
    and <value> are lexical tokens, whereas <expr> is syntax.

    However ...

    Knowing that yacc and bison CAN handle characters as tokens, and
    assuming you have defined <digit> and <digits> elsewhere in your
    grammar, neither yacc nor bison can find this kind of equivalence. In
    yacc it will result in a reduce/reduce error. In bison what happens
    depends on the kind of parser you asked for (LALR,SLR,LR,GLR), but in
    any case the result won't be pretty.


    Assuming instead that you meant for <number> and <value> to be
    recognized by the lexer rather than the parser ... flex (not lex)
    could discover that <number> and <value> are equivalent, but since
    they would lead to different actions: returning a different token -
    both would included in the DFA. However, whichever one happened to be
    tried first would be the only one that ever was recognized, and your
    parser would only ever get one of the expected tokens.


    yacc and lex are ancient and haven't been maintained for decades. They
    have limitations that will never be addressed, and bugs that will
    never be fixed. If you really need backward compatibility with them,
    it is available using bison and flex ... and if you don't need bacward
    compatibility, bison and flex are much more powerful and modern tools
    for use with new projects.
    [There are other modern alternatives to yacc and lex also, but
    discussion of them is beyond the scope of this missive.]

    Now, to the question at hand (or, a version thereof).

    It's relatively common to design an algorithm or a hardware machine
    as a "state transition diagram" and then reduce to an implementation.
    (The question, posed here, is how much this can be automated and
    the benefits that might acrue).

    Algorithms for turning graphs into table driven FSM, or equivalently a
    switch / case statement, are well known.

    Assuming an appropriate graphical IDE, a designer certainly could
    specify a state graph and code for actions, and have a program
    generated from it. Given the right input from the designer, a great
    deal of checking could be done against the graph to verify that it
    covers enumerated inputs and transitions, that specified inputs lead
    to specified actions, that action code exists, etc.

    But what is NOT possible is to verify that all /possible/ inputs and
    state transitions have been enumerated. Nor is it possible to verify
    that required actions have been specified, or necessarily that the
    actions are being taken in proper context ... those are things for
    which the tool simply MUST trust the graph designer.


    George

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Tue Mar 14 19:40:07 2023
    On 3/14/2023 6:29 PM, George Neuner wrote:
    On Mon, 13 Mar 2023 22:59:25 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/13/2023 8:07 PM, George Neuner wrote:
    On Mon, 13 Mar 2023 15:41:32 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    lex/yacc are more applicable to parsing strings of characters
    as you'd encounter in a (programming) language.

    AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc.

    yacc and bison will remove unreachable PDA states. Unreachable states
    in the parser typically are not deliberate but rather result from
    ambiguities in the grammar. lex, being LALR(1), does not tolerate
    much ambiguity - however bison can generate more powerful LR(1) and
    GLR(1) parsers, and these are far more likely to have unreachable
    states accidentally generated.

    Similarly, lex will remove unreachable DFA states. Again, these
    typically are not deliberate, but rather result from lex combining the
    various input regex into a single DFA having multiple entry points and
    multiple accepting states.

    But, the FSM parallel would be an "orphan" state that would (*likely*)
    be readily apparent in a state diagram. "You can't get there from here".

    In hardware designs, it is not uncommon to have multiple equivalent
    states that aren't easily recognized as such (because you tend to
    ignore "don't cares" -- which are ripe for collapsing equivalents).

    When you merge two FSM you often get redundant "don't care" nodes, but
    you also can get nodes which either are impossible to enter [dead
    code], or impossible to leave [halt], because there are no legal
    transitions that will permit it. Joining FSM involves identifying and
    pruning both types of nodes.

    Then how did you decide they were equivalent? Clearly, (at least)
    one had a different set of options/transitions that is not supported
    in the "merged" implementation.

    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in
    the same action. And finally, flex can [sometimes] discover
    "meta-equivalence" classes: commonly expected sequences of characters
    and/or other equivalence classes.

    But, those are mapping equivalent *inputs* together, not equivalent
    *states*. I.e., treating "BEGIN" and "begin" as equivalent.

    No. Consider the case of just recognizing a decimal digit: compare
    the graph using the alternation: (0|1|2|3|4|5|6|7|8|9), vs the graph
    using the class [:digit:].

    Using the OR alternation, including start you have 11 nodes. Start has
    10 transitions exiting, and each digit node has a single transition
    entering.

    Are you using "node" as a synonym for "state"?

    E.g.,

    State Powered_Up
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    ..



    State Operation_Complete
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    ..


    In each of these, I would have a single transition exiting the
    state labeled "0+1+2+3+4+5+6+7+8+9" invoking Accept_Digit()
    and destined for "Entry"

    Operation_Complete (i.e., the *end* of some undisclosed sequence
    of actions, preparing to restart on another iteration) is equivalent
    to Powered_Up -- presumably the state just before that sequence of
    actions is first started (assuming "..." are equivalent)

    I can detect this with an implication table. Even if a series of
    states are involved (step1, step2, step3) An algorithm can
    similarly detect it. And, remove one of these two states (or
    series of states) from the machine (for example, "Operation_Complete"), replacing all references to it in the machine with "Powered_Up".

    Doing so *silently* represents a risk; it could be that the
    two states are intended to be different -- because something
    in the "..." is handled differently AND THE DEVELOPER HAS
    FORGOTTEN TO ADD THAT! So, any tool that makes that optimization
    has to alert the developer that it is altering the given
    machine definition in a way that it *thinks* is permissible.

    Using the digit class, you have 2 nodes, with 10 transitions that all
    get you from start to the digit class node.

    I don't see where the extra nodes (states) come from.

    Obviously this is simplistic, because the members of the character
    class form a subgraph which itself has to be recognized. The
    important point here is that the subgraph as a whole can represent a
    /single/ node in a much more complex graph - its constituent
    characters need not be repeated in the complex graph. More on this
    below.

    A complex DFA that combines many different regex may present other opportunities to recognize given (possibly arbitrary) sets of
    characters - opportunites that may not be apparent from looking at the constituent regex.

    Would it recognize a common sequence of state transitions
    that occurs in two different places in the grammar? E.g.,
    specifying the syntax for a numeric quantity in two different
    places only to realize that it's actually the same part of
    the grammar expressed as two different instances?

    When given the option to find equivalence classes, flex can identify
    sets of characters that are used repeatedly. Those characters are
    gathered into an "equivalence" that then can be a node in the DFA
    instead of redundantly repeating individual characters.

    OK, but that just replaces N *identical*, "parallel" transitions
    with a single one labeled with a class instead of a discrete value.
    Just like "0+1+2+3+4+5+6+7+8+9". It hasn't reduced the number of states.

    Remember DFA are deterministic - a node can't take different actions depending on which of multiple transitions entered (or left) it ... so
    if you want the same character to be recognized in a different context (leading to a different action), you must repeat it in a different
    node.

    This is where being able to identify, essentially arbitrary, sets of character and coalesce them into a recognizer "class" is useful. If a
    given set of N(>1) characters is used M times in the graph, then by coalescing them you remove M(N-1) nodes from your graph. The number
    of /transitions/ in the graph remains the same, but recall that it is
    the /nodes/ that consume space in the lexer tables.

    <number> ::= <digit><digits>

    <value> ::= <digit><digits>

    <expr> ::= <number> <op> <number> | <value> <op> <value>

    (silly example; but the inputs in each case are the same)

    You're mixing abstraction levels here: <digit>, <digits>, <number>,
    and <value> are lexical tokens, whereas <expr> is syntax.

    I'm making the point that <number> and <value> are equivalent
    results. A machine that determines if an input satisfied
    one would similarly satisfy the other.

    So, the two machines would be duplicates of each other and
    subject to being optimized into one (because <expr> treats
    <number> and <value> as equivalent in *its* machine.

    However ...

    Knowing that yacc and bison CAN handle characters as tokens, and
    assuming you have defined <digit> and <digits> elsewhere in your
    grammar, neither yacc nor bison can find this kind of equivalence. In
    yacc it will result in a reduce/reduce error. In bison what happens
    depends on the kind of parser you asked for (LALR,SLR,LR,GLR), but in
    any case the result won't be pretty.

    Assuming instead that you meant for <number> and <value> to be
    recognized by the lexer rather than the parser ... flex (not lex)

    Then the question would be whether or not the lexer could
    recognize these "little machines" were equivalent and rewite
    the grammar so only one was present.

    could discover that <number> and <value> are equivalent, but since
    they would lead to different actions: returning a different token -
    both would included in the DFA. However, whichever one happened to be
    tried first would be the only one that ever was recognized, and your
    parser would only ever get one of the expected tokens.

    yacc and lex are ancient and haven't been maintained for decades. They
    have limitations that will never be addressed, and bugs that will
    never be fixed. If you really need backward compatibility with them,
    it is available using bison and flex ... and if you don't need bacward
    compatibility, bison and flex are much more powerful and modern tools
    for use with new projects.
    [There are other modern alternatives to yacc and lex also, but
    discussion of them is beyond the scope of this missive.]

    Now, to the question at hand (or, a version thereof).

    It's relatively common to design an algorithm or a hardware machine
    as a "state transition diagram" and then reduce to an implementation.
    (The question, posed here, is how much this can be automated and
    the benefits that might acrue).

    Algorithms for turning graphs into table driven FSM, or equivalently a
    switch / case statement, are well known.

    Of course. But, they can't *create* information. Anything that
    they synthesize has to be present in the input.

    E.g., I can convert an ebnf to a railroad diagram. But, I can't
    add information on the actions to be performed when each rule
    in the grammar is applied (because the ebnf doesn't include that!)

    Working backwards, you can't extract information from that railroad
    diagram -- to include in the generated code -- that isn't there!

    Assuming an appropriate graphical IDE, a designer certainly could
    specify a state graph and code for actions, and have a program
    generated from it. Given the right input from the designer, a great
    deal of checking could be done against the graph to verify that it
    covers enumerated inputs and transitions, that specified inputs lead
    to specified actions, that action code exists, etc.

    But what is NOT possible is to verify that all /possible/ inputs and
    state transitions have been enumerated. Nor is it possible to verify
    that required actions have been specified, or necessarily that the
    actions are being taken in proper context ... those are things for
    which the tool simply MUST trust the graph designer.

    And, as above, if you want the code to do X, then, somehow, you must
    put "X" into the graph. In the examples, here, we've lumped everything
    that needs to be "done" into single function calls associated with each
    of the transitions. As a single verb-noun is unlikely to be capable of describing much detail, you're still stuck with code -- that the developer
    had to explicitly write.

    The dream that you can convert a drawing into an arbitrary *application*
    exists as a pipe.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Wed Mar 22 16:37:53 2023
    Hi Don,

    Sorry for the delay here ... you know what's going on.


    On Tue, 14 Mar 2023 19:40:07 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/14/2023 6:29 PM, George Neuner wrote:
    On Mon, 13 Mar 2023 22:59:25 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/13/2023 8:07 PM, George Neuner wrote:

    When you merge two FSM you often get redundant "don't care" nodes, but
    you also can get nodes which either are impossible to enter [dead
    code], or impossible to leave [halt], because there are no legal
    transitions that will permit it. Joining FSM involves identifying and
    pruning both types of nodes.

    Then how did you decide they were equivalent? Clearly, (at least)
    one had a different set of options/transitions that is not supported
    in the "merged" implementation.

    You merge multiple DFA by constructing an equivalent NDFA where all
    the transitions that lead to the same action are coalesced into a
    single node (effectively eliminating the redundancies). Some of the
    impossible halt states may also be eliminated as redundancies.

    Once the minimum state NDFA is built, you turn /that/ back into a DFA
    to increase performance.


    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in
    the same action. And finally, flex can [sometimes] discover
    "meta-equivalence" classes: commonly expected sequences of characters
    and/or other equivalence classes.

    But, those are mapping equivalent *inputs* together, not equivalent
    *states*. I.e., treating "BEGIN" and "begin" as equivalent.

    No. Consider the case of just recognizing a decimal digit: compare
    the graph using the alternation: (0|1|2|3|4|5|6|7|8|9), vs the graph
    using the class [:digit:].

    Using the OR alternation, including start you have 11 nodes. Start has
    10 transitions exiting, and each digit node has a single transition
    entering.

    Are you using "node" as a synonym for "state"?

    I am using graph terminology - mostly because all FA builders start by constructing a state /graph/. After the graph is complete, it may be implemented using tables, or Duff's device, etc. ... but it doesn't
    start that way. 8-)


    E.g.,

    State Powered_Up
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    ..

    State Operation_Complete
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    ..

    In each of these, I would have a single transition exiting the
    state labeled "0+1+2+3+4+5+6+7+8+9" invoking Accept_Digit()
    and destined for "Entry"

    Operation_Complete (i.e., the *end* of some undisclosed sequence
    of actions, preparing to restart on another iteration) is equivalent
    to Powered_Up -- presumably the state just before that sequence of
    actions is first started (assuming "..." are equivalent)

    I can detect this with an implication table. Even if a series of
    states are involved (step1, step2, step3) An algorithm can
    similarly detect it. And, remove one of these two states (or
    series of states) from the machine (for example, "Operation_Complete"), >replacing all references to it in the machine with "Powered_Up".

    Absolutely it /could/ be done with table based algorithms, but I've
    never seen it done that way in any real (non toy) implementation ...
    graph based solutions scale better to large problems.


    Using the digit class, you have 2 nodes, with 10 transitions that all
    get you from start to the digit class node.

    I don't see where the extra nodes (states) come from.

    Think about the "class" as a state in an /NDFA/ ... there are multiple transitions that can get you in - one transition (action) that gets
    you out.

    When the "class" is implemented it becomes (for lack of better term) a subroutine (subgraph) in the resulting DFA. The more the subgraph can
    be reused, the more savings in the DFA.



    Obviously this is simplistic, because the members of the character
    class form a subgraph which itself has to be recognized. The
    important point here is that the subgraph as a whole can represent a
    /single/ node in a much more complex graph - its constituent
    characters need not be repeated in the complex graph. More on this
    below.

    A complex DFA that combines many different regex may present other
    opportunities to recognize given (possibly arbitrary) sets of
    characters - opportunites that may not be apparent from looking at the
    constituent regex.

    Would it recognize a common sequence of state transitions
    that occurs in two different places in the grammar? E.g.,
    specifying the syntax for a numeric quantity in two different
    places only to realize that it's actually the same part of
    the grammar expressed as two different instances?

    When given the option to find equivalence classes, flex can identify
    sets of characters that are used repeatedly. Those characters are
    gathered into an "equivalence" that then can be a node in the DFA
    instead of redundantly repeating individual characters.

    OK, but that just replaces N *identical*, "parallel" transitions
    with a single one labeled with a class instead of a discrete value.
    Just like "0+1+2+3+4+5+6+7+8+9". It hasn't reduced the number of states.

    It will reduce the number of states IF the class is reused with the
    same action. Consider

    integer: [+-]?[:digit:]+
    float: (((integer)\.)|((integer)?\.(integer)))(\e(integer))?

    The [:digit:] class is resused 5 times. The definition of "integer"
    also forms a (5x) reused meta-class that flex could recognize if told
    to look for them.

    Since, in this example, the [:digit:] class is always used with the
    same action, it will be implemented in the DFA state graph just once.
    Since the class itself consists of ~13 states: START that waits for
    input, 0..9 that accept input, common EXIT, and common ERROR out if
    something else is input ... treating it AS a class saves 52 states (13
    x 4) in the state graph.

    The common exit and error states out may be eliminated from the final
    FA (assuming no conflicting uses of [:digit:], but they will be
    included in initial construction of the state graph (think of them
    like subroutine preamble/postamble).


    Remember DFA are deterministic - a node can't take different actions
    depending on which of multiple transitions entered (or left) it ... so
    if you want the same character to be recognized in a different context
    (leading to a different action), you must repeat it in a different
    node.



    <number> ::= <digit><digits>

    <value> ::= <digit><digits>

    <expr> ::= <number> <op> <number> | <value> <op> <value>

    (silly example; but the inputs in each case are the same)

    You're mixing abstraction levels here: <digit>, <digits>, <number>,
    and <value> are lexical tokens, whereas <expr> is syntax.

    I'm making the point that <number> and <value> are equivalent
    results. A machine that determines if an input satisfied
    one would similarly satisfy the other.

    So, the two machines would be duplicates of each other and
    subject to being optimized into one (because <expr> treats
    <number> and <value> as equivalent in *its* machine.

    Yes they are duplicates at some level of abstraction, but that level
    is above what the tool can recognize and deal with. The programmer
    labeled them differently and so the tool has to assume both are
    required, even if in use there is no practical way to distinguish them
    in the input.



    Knowing that yacc and bison CAN handle characters as tokens, and
    assuming you have defined <digit> and <digits> elsewhere in your
    grammar, neither yacc nor bison can find this kind of equivalence. In
    yacc it will result in a reduce/reduce error. In bison what happens
    depends on the kind of parser you asked for (LALR,SLR,LR,GLR), but in
    any case the result won't be pretty.

    Assuming instead that you meant for <number> and <value> to be
    recognized by the lexer rather than the parser ... flex (not lex)

    Then the question would be whether or not the lexer could
    recognize these "little machines" were equivalent and rewite
    the grammar so only one was present.

    You'd need a combination tool that produced parser and lexer from a
    unfied spec. There are such tools: e.g., ANTLR ... but I'm not aware
    of any tools that do /that/ kind of optimization.


    It's all about the context: there's no practical way to merge
    identical recognizers if they directly lead to different actions. In
    the examples above, [:digit:] could be reused only because every use
    of it simply accumulated another input character ... the differences
    occurred when a non-digit character was entered.

    If instead you did something like:

    integer: [:digit:] return 'i'
    hex: [:digit:]|['a'-'f'] return 'h';

    This would blow up in your face because 0..9 would never be recognized
    a hex digit, but more importantly the 2 uses of the class lead
    /immediately/ to different actions so the class subroutine (subgraph)
    would have to be repeated in the FA with different exit actions.


    could discover that <number> and <value> are equivalent, but since
    they would lead to different actions: returning a different token -
    both would included in the DFA. However, whichever one happened to be
    tried first would be the only one that ever was recognized, and your
    parser would only ever get one of the expected tokens.



    Now, to the question at hand (or, a version thereof).

    It's relatively common to design an algorithm or a hardware machine
    as a "state transition diagram" and then reduce to an implementation.
    (The question, posed here, is how much this can be automated and
    the benefits that might acrue).

    Algorithms for turning graphs into table driven FSM, or equivalently a
    switch / case statement, are well known.

    Of course. But, they can't *create* information. Anything that
    they synthesize has to be present in the input.

    E.g., I can convert an ebnf to a railroad diagram. But, I can't
    add information on the actions to be performed when each rule
    in the grammar is applied (because the ebnf doesn't include that!)

    Working backwards, you can't extract information from that railroad
    diagram -- to include in the generated code -- that isn't there!

    Assuming an appropriate graphical IDE, a designer certainly could
    specify a state graph and code for actions, and have a program
    generated from it. Given the right input from the designer, a great
    deal of checking could be done against the graph to verify that it
    covers enumerated inputs and transitions, that specified inputs lead
    to specified actions, that action code exists, etc.

    But what is NOT possible is to verify that all /possible/ inputs and
    state transitions have been enumerated. Nor is it possible to verify
    that required actions have been specified, or necessarily that the
    actions are being taken in proper context ... those are things for
    which the tool simply MUST trust the graph designer.

    And, as above, if you want the code to do X, then, somehow, you must
    put "X" into the graph. In the examples, here, we've lumped everything
    that needs to be "done" into single function calls associated with each
    of the transitions. As a single verb-noun is unlikely to be capable of >describing much detail, you're still stuck with code -- that the developer >had to explicitly write.

    The dream that you can convert a drawing into an arbitrary *application* >exists as a pipe.

    Absolutely: somehow actions have to be specified, whether as arbitrary
    user entered code attached to graph nodes, or as predefined "action"
    nodes linked into the graph.

    But as I said above, there are things that simply can't be checked
    without embedding significant domain knowledge into the tool itself.
    That essentially precludes any notion of a generic tool ... even if
    the tool included an expert system, it's likely that no generic
    interface to the expert system could be designed that would
    satisfactorily deal with the needs of many different domains.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Wed Mar 22 18:15:43 2023
    On 3/22/2023 1:37 PM, George Neuner wrote:
    Then there is flex.

    flex has some DFA optimizations available. First, flex can compress
    the data tables which encode its DFA states. Second, flex can
    discover "equivalence" classes: groups of characters which result in >>>>> the same action. And finally, flex can [sometimes] discover
    "meta-equivalence" classes: commonly expected sequences of characters >>>>> and/or other equivalence classes.

    But, those are mapping equivalent *inputs* together, not equivalent
    *states*. I.e., treating "BEGIN" and "begin" as equivalent.

    No. Consider the case of just recognizing a decimal digit: compare
    the graph using the alternation: (0|1|2|3|4|5|6|7|8|9), vs the graph
    using the class [:digit:].

    Using the OR alternation, including start you have 11 nodes. Start has
    10 transitions exiting, and each digit node has a single transition
    entering.

    Are you using "node" as a synonym for "state"?

    I am using graph terminology - mostly because all FA builders start by constructing a state /graph/. After the graph is complete, it may be implemented using tables, or Duff's device, etc. ... but it doesn't
    start that way. 8-)

    I build FSMs similarly. But, you can't commit graphs to
    ASCII text whereas tables are a natural consequence.

    The difference seems largely to be that DFA are geared towards
    expressing "languages" (sequences of symbols) whereas FSMs
    are geared towards expressing sequences of events/actions.

    E.g., you can build an expression to describe the legal
    symbol sequences to create a particular type of token
    (NUMBER, BEGIN, END, etc.) with the assumption that every
    symbol *in* those tokens is handled by a single action
    (accumulate_digit).

    By contrast, an FSM will often have a variety of very different
    symbols recognized in a given state and acted upon differently
    (POWER_FAIL, POWER_RESTORED, LOW_BATTERY, CLEAR, ENTER, BARCODE_DETECTED, etc.). These tend to have more "work" associated with their
    recognition than a set of equivalent symbols (e.g., digits).

    And, while each may be handled differently from the others,
    they tend to be handled the same way when encountered in
    different states. I.e., a POWER_FAIL is processed the same
    each place it is considered significant.

    So, you want a way of expressing this common set of conditions/symbols/events in a way that can be reused in many different states.

    E.g.,

    State Powered_Up
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    Check Exceptions
    ..

    State Operation_Complete
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    Check Exceptions
    ..

    State Exceptions (this is a misnomer but used for syntactic simplicity) On Barcode_Detected Goto HandleBarcode Executing OverwriteAccumulator() On Power_Fail Goto SignalOutage Executing AlertOperator()
    On Low_Battery Goto SafeguardData Executing OrderlyShutdown()

    In each of these, I would have a single transition exiting the
    state labeled "0+1+2+3+4+5+6+7+8+9" invoking Accept_Digit()
    and destined for "Entry"

    Operation_Complete (i.e., the *end* of some undisclosed sequence
    of actions, preparing to restart on another iteration) is equivalent
    to Powered_Up -- presumably the state just before that sequence of
    actions is first started (assuming "..." are equivalent)

    I can detect this with an implication table. Even if a series of
    states are involved (step1, step2, step3) An algorithm can
    similarly detect it. And, remove one of these two states (or
    series of states) from the machine (for example, "Operation_Complete"),
    replacing all references to it in the machine with "Powered_Up".

    Absolutely it /could/ be done with table based algorithms, but I've
    never seen it done that way in any real (non toy) implementation ...
    graph based solutions scale better to large problems.

    Using the digit class, you have 2 nodes, with 10 transitions that all
    get you from start to the digit class node.

    I don't see where the extra nodes (states) come from.

    Think about the "class" as a state in an /NDFA/ ... there are multiple transitions that can get you in - one transition (action) that gets
    you out.

    When the "class" is implemented it becomes (for lack of better term) a subroutine (subgraph) in the resulting DFA. The more the subgraph can
    be reused, the more savings in the DFA.

    Obviously this is simplistic, because the members of the character
    class form a subgraph which itself has to be recognized. The
    important point here is that the subgraph as a whole can represent a
    /single/ node in a much more complex graph - its constituent
    characters need not be repeated in the complex graph. More on this
    below.

    A complex DFA that combines many different regex may present other
    opportunities to recognize given (possibly arbitrary) sets of
    characters - opportunites that may not be apparent from looking at the
    constituent regex.

    Would it recognize a common sequence of state transitions
    that occurs in two different places in the grammar? E.g.,
    specifying the syntax for a numeric quantity in two different
    places only to realize that it's actually the same part of
    the grammar expressed as two different instances?

    When given the option to find equivalence classes, flex can identify
    sets of characters that are used repeatedly. Those characters are
    gathered into an "equivalence" that then can be a node in the DFA
    instead of redundantly repeating individual characters.

    OK, but that just replaces N *identical*, "parallel" transitions
    with a single one labeled with a class instead of a discrete value.
    Just like "0+1+2+3+4+5+6+7+8+9". It hasn't reduced the number of states.

    It will reduce the number of states IF the class is reused with the
    same action. Consider

    integer: [+-]?[:digit:]+
    float: (((integer)\.)|((integer)?\.(integer)))(\e(integer))?

    The [:digit:] class is resused 5 times. The definition of "integer"
    also forms a (5x) reused meta-class that flex could recognize if told
    to look for them.

    OK. It's a special case of alternation *and* equivalence.
    I build "state subroutines" to handle sets of symbols that
    are handled the same way but "invoked" from different states
    (see Exceptions). But, the individual symbols can invoke
    different actions *and* different next states -- as long as
    they are consistent in each "application".

    Since, in this example, the [:digit:] class is always used with the
    same action, it will be implemented in the DFA state graph just once.
    Since the class itself consists of ~13 states: START that waits for
    input, 0..9 that accept input, common EXIT, and common ERROR out if
    something else is input ... treating it AS a class saves 52 states (13
    x 4) in the state graph.

    The common exit and error states out may be eliminated from the final
    FA (assuming no conflicting uses of [:digit:], but they will be
    included in initial construction of the state graph (think of them
    like subroutine preamble/postamble).

    Remember DFA are deterministic - a node can't take different actions
    depending on which of multiple transitions entered (or left) it ... so
    if you want the same character to be recognized in a different context
    (leading to a different action), you must repeat it in a different
    node.

    <number> ::= <digit><digits>

    <value> ::= <digit><digits>

    <expr> ::= <number> <op> <number> | <value> <op> <value>

    (silly example; but the inputs in each case are the same)

    You're mixing abstraction levels here: <digit>, <digits>, <number>,
    and <value> are lexical tokens, whereas <expr> is syntax.

    I'm making the point that <number> and <value> are equivalent
    results. A machine that determines if an input satisfied
    one would similarly satisfy the other.

    So, the two machines would be duplicates of each other and
    subject to being optimized into one (because <expr> treats
    <number> and <value> as equivalent in *its* machine.

    Yes they are duplicates at some level of abstraction, but that level
    is above what the tool can recognize and deal with.

    That was what I was hoping *might* be possible. I can automate
    implication analysis to detect and *suggest* these equivalence
    reductions (cuz it's a simple process and my tables reduce to
    just simple blocks of tuples).

    But, I don't want the tool to automatically make that reduction
    because the developer may not have *intended* the two states to be
    equivalent. I.e., claiming that they are equivalent alerts the
    developer of the fact that he may have forgotten something
    (or, incorrectly specified a symbol/action).

    OTOH, for nontrivial FSM, it is often too hard for a developer to
    recognize sequences of states that *can* be equivalenced -- esp
    with the notational shorthands that I've developed.

    E.g.,

    State Operation_Finished
    On Barcode_Detected Goto HandleBarcode Executing OverwriteAccumulator() On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On Power_Fail Goto SignalOutage Executing AlertOperator()
    On Low_Battery Goto SafeguardData Executing OrderlyShutdown()
    On '4' Goto Entry Executing Accept_Digit()
    On '5' Goto Entry Executing Accept_Digit()
    On '6' Goto Entry Executing Accept_Digit()
    On '7' Goto Entry Executing Accept_Digit()
    On '8' Goto Entry Executing Accept_Digit()
    On '9' Goto Entry Executing Accept_Digit()
    .

    is equivalent to Operation_Complete. And, would be so if it
    additionally (redundantly) listed "Check Exceptions"

    Finally, mnemonics for symbols resolve to specific values.
    So, there's nothing that prevents a developer from assigning
    two mnemonics to the same value -- intentionally or accidentally.

    [I have some mechanisms that minimize the chance of this
    happening. But, there's nothing to prevent me from defining
    two semantically equivalent symbols that get bound to
    different values -- e.g., Power_Fail and Power_Die]

    The programmer
    labeled them differently and so the tool has to assume both are
    required, even if in use there is no practical way to distinguish them
    in the input.

    Knowing that yacc and bison CAN handle characters as tokens, and
    assuming you have defined <digit> and <digits> elsewhere in your
    grammar, neither yacc nor bison can find this kind of equivalence. In
    yacc it will result in a reduce/reduce error. In bison what happens
    depends on the kind of parser you asked for (LALR,SLR,LR,GLR), but in
    any case the result won't be pretty.

    Assuming instead that you meant for <number> and <value> to be
    recognized by the lexer rather than the parser ... flex (not lex)

    Then the question would be whether or not the lexer could
    recognize these "little machines" were equivalent and rewite
    the grammar so only one was present.

    You'd need a combination tool that produced parser and lexer from a
    unfied spec. There are such tools: e.g., ANTLR ... but I'm not aware
    of any tools that do /that/ kind of optimization.

    It's all about the context: there's no practical way to merge
    identical recognizers if they directly lead to different actions. In
    the examples above, [:digit:] could be reused only because every use
    of it simply accumulated another input character ... the differences
    occurred when a non-digit character was entered.

    Yes. My point wrt FSM having lots of different input symbols and
    different associated next-states/actions.

    If instead you did something like:

    integer: [:digit:] return 'i'
    hex: [:digit:]|['a'-'f'] return 'h';

    This would blow up in your face because 0..9 would never be recognized
    a hex digit, but more importantly the 2 uses of the class lead
    /immediately/ to different actions so the class subroutine (subgraph)
    would have to be repeated in the FA with different exit actions.

    Yes. If the tool places an implicit priority on the rules
    based on the order in which they are encountered. I intentionally
    don't specify this in the design of the tables, leaving the
    "post processor" some latitude in how it implements them
    and the runtime some potential efficiencies.

    [This also lets me embed ambiguities -- for certain clients :> ]

    E.g.,
    State Operation
    On Barcode_Detected Goto HandleBarcode Executing OverwriteAccumulator() On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '3' Goto Entry Executing Accept_Digit()

    and

    State Operation
    On '1' Goto Entry Executing Accept_Digit()
    On '2' Goto Entry Executing Accept_Digit()
    On '1' Goto Entry Executing Reject_Digit()
    On '3' Goto Entry Executing Accept_Digit()
    On Barcode_Detected Goto HandleBarcode Executing OverwriteAccumulator()

    can be made equivalent -- or different. Barcodes could take priority
    over individual digits -- or not. Etc. The dispatch can trade
    speed for space -- or vice versa.

    [Of course, any parser generator can offer similar options.
    But, as most parsers are used in a different context, the
    tradeoffs aren't as productive]

    Assuming an appropriate graphical IDE, a designer certainly could
    specify a state graph and code for actions, and have a program
    generated from it. Given the right input from the designer, a great
    deal of checking could be done against the graph to verify that it
    covers enumerated inputs and transitions, that specified inputs lead
    to specified actions, that action code exists, etc.

    But what is NOT possible is to verify that all /possible/ inputs and
    state transitions have been enumerated. Nor is it possible to verify
    that required actions have been specified, or necessarily that the
    actions are being taken in proper context ... those are things for
    which the tool simply MUST trust the graph designer.

    And, as above, if you want the code to do X, then, somehow, you must
    put "X" into the graph. In the examples, here, we've lumped everything
    that needs to be "done" into single function calls associated with each
    of the transitions. As a single verb-noun is unlikely to be capable of
    describing much detail, you're still stuck with code -- that the developer >> had to explicitly write.

    The dream that you can convert a drawing into an arbitrary *application*
    exists as a pipe.

    Absolutely: somehow actions have to be specified, whether as arbitrary
    user entered code attached to graph nodes, or as predefined "action"
    nodes linked into the graph.

    And, the actions must be *complete*. Or, you have to drag in
    another abstraction mechanism.

    A state diagram for a hardware machine is complete as it spells out
    all of the gazintas and cumzoutas.

    A railroad diagram effectively describes *syntax* but rarely much
    more.

    A "programming tool" with the same level of visibility would
    likely have a boatload of cryptic syntax/notations that would
    make reliable use of the tool dubious (unless a full-time job).

    But as I said above, there are things that simply can't be checked
    without embedding significant domain knowledge into the tool itself.
    That essentially precludes any notion of a generic tool ... even if
    the tool included an expert system, it's likely that no generic
    interface to the expert system could be designed that would
    satisfactorily deal with the needs of many different domains.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Sun Mar 26 00:45:09 2023
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    On 3/22/2023 1:37 PM, George Neuner wrote:

    I build FSMs similarly. But, you can't commit graphs to
    ASCII text whereas tables are a natural consequence.

    Actually you can serialize graphs as text, but the result may be
    varying degrees of difficult to read for a human.


    Trivial example: fire up Racket and enter

    #lang racket
    (require racket/serialize)
    (let (
    [outer (mcons 0 (mcons 1 (mcons 2 (mcons 3 '()))))]
    [inner (mcons 9 (mcons 8 (mcons 7 '())))]
    [cycle '()]
    )
    (writeln outer)
    (writeln inner)
    (set-mcdr! (mcdr (mcdr (mcdr outer))) outer)
    (set-mcdr! (mcdr (mcdr inner)) inner)
    (set-mcar! (mcdr outer) inner)
    (writeln outer)
    (writeln (serialize outer))
    (writeln (deserialize (serialize outer)))
    )

    This creates a simple graph having 2 cycles and prints it. See what
    happens. The result of (serialize _) is a string that can be saved to
    a file. You can substitute structs for mcons (mutable cons) if you
    want to experiment with making more complicated graphs.


    Tables are great as implementation ... but for a construction tool
    that needs to store and modify graphs, it /can/ be a pain to
    reconstruct complicated graphs from table representations. It's a
    hell of a lot easier to read back a suitably serialized form.


    The difference seems largely to be that DFA are geared towards
    expressing "languages" (sequences of symbols) whereas FSMs
    are geared towards expressing sequences of events/actions.

    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a
    particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.

    Purely as a techical matter, (f)lex can create general FA assuming
    that transition conditions can be represented as character input to
    the reader. The "reader" function is completely redefineable: the
    default is to read from STDIN, but, in fact, a custom reader can do
    absolutely anything under the hood so long as it returns a character
    (or EOF) when called.

    In practice you would not want to do this. A decent UML tool would be
    a much better choice.


    By contrast, an FSM will often have a variety of very different
    symbols recognized in a given state and acted upon differently
    (POWER_FAIL, POWER_RESTORED, LOW_BATTERY, CLEAR, ENTER, BARCODE_DETECTED, >etc.). These tend to have more "work" associated with their
    recognition than a set of equivalent symbols (e.g., digits).

    And, while each may be handled differently from the others,
    they tend to be handled the same way when encountered in
    different states. I.e., a POWER_FAIL is processed the same
    each place it is considered significant.

    Yes. And you could (at least theoretically) represent this in flex by
    encoding POWER_FAIL, etc. as characters or strings and sending those
    characters or strings as input to the reader when those events occur.
    Internal state transitions can be handled the same way: send
    characters to the reader.

    Again, this is an abuse of the tool. Just because you can do it does
    not mean you should do it.


    I build "state subroutines" to handle sets of symbols that
    are handled the same way but "invoked" from different states
    (see Exceptions). But, the individual symbols can invoke
    different actions *and* different next states -- as long as
    they are consistent in each "application".

    flex (not lex) permits defining contextual "start" states, which the
    code can arbitrarily switch among. The same input can be treated
    differently in different start states. These really are coroutines -
    not subroutines - and the user code decides which state to switch to
    next, but flex does provides a stack so you can use them as
    subroutines (without having to track the nesting yourself).


    If instead you did something like:

    integer: [:digit:] return 'i'
    hex: [:digit:]|['a'-'f'] return 'h';

    This would blow up in your face because 0..9 would never be recognized
    a hex digit, but more importantly the 2 uses of the class lead
    /immediately/ to different actions so the class subroutine (subgraph)
    would have to be repeated in the FA with different exit actions.

    Yes. If the tool places an implicit priority on the rules
    based on the order in which they are encountered. I intentionally
    don't specify this in the design of the tables, leaving the
    "post processor" some latitude in how it implements them
    and the runtime some potential efficiencies.

    The tool places priority on the longest, most precise match. It falls
    back on definition order when the input - as given - matches multiple
    patterns.

    But again, start states can (sometimes) be used to get around this
    behavior.



    George

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Sun Mar 26 03:35:27 2023
    On 3/25/2023 9:45 PM, George Neuner wrote:
    The difference seems largely to be that DFA are geared towards
    expressing "languages" (sequences of symbols) whereas FSMs
    are geared towards expressing sequences of events/actions.

    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    Yes, though (IME) taught to different audiences and in different ways.

    My hardware classes talked about FSMs, Meely/Moore, "state diagrams"
    and optimization techniques.

    My software classes talked about DFAs, EBNFs, *railroad* diagrams
    but never a mention of optimization tools or techniques.

    They also seem to be applied differently. E.g., in a (hardware) FSM,
    it is not uncommon to list a logical expression as the stimulus
    for a transition (e.g., "LineFeed & /Last_Line" vs. "LineFeed & LastLine" directing the machine to two different states with two different outputs
    or actions). In DFAs, it was always just sequences of symbols -- the
    sorts of things that would specify a grammar (inherently serial, one-at-a-time "conditions").

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.

    From:
    "AFAICT, none of these tools knows how to optimize states by
    noting equivalences (?) [George would know for sure]
    OTOH, when dealing with hardware machines, it's a common step
    to reduce via implication tables, etc."

    I.e., one could express a FSM as a yacc grammar -- write an "action
    routine" (admittedly, probably very terse due to the format of a yacc
    file) for each "symbol" as if symbols were events/input conditions.
    So, conceivably, the lexer could generate LF_Not_LastLine and
    LF_LastLine symbols that the yacc parser could act on (with suitable
    actions assigned on the "right hand side")

    Given this duality, the pertinence of my above comment is evident:
    could yacc (et al.) identify the same sorts of equivalent states
    that I would identify and eliminate with implication table analysis
    if it was an FSM?

    Moreover, yacc and bison do NOT implement a general FA, but rather a particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.

    Purely as a techical matter, (f)lex can create general FA assuming
    that transition conditions can be represented as character input to
    the reader. The "reader" function is completely redefineable: the
    default is to read from STDIN, but, in fact, a custom reader can do absolutely anything under the hood so long as it returns a character
    (or EOF) when called.

    Therein lies a notable limitation. In a (hardware) FSM, there are no limits
    to the number of inputs that can CONCURRENTLY be examined by the machine.
    E.g., I could label a transition with:
    A*/B*/C*D*E*F*/G*H*I*J*/K*/L*/M + N*O*P*Q + /R*/S*/T*U*V + W + X*Y*Z
    To represent this to lex/yacc, I would have to reduce it to a "narrow"
    symbol -- possible if there are only a limited number of such combinations
    in the grammar (as sourced by the lexer).

    In practice you would not want to do this. A decent UML tool would be
    a much better choice.

    By contrast, an FSM will often have a variety of very different
    symbols recognized in a given state and acted upon differently
    (POWER_FAIL, POWER_RESTORED, LOW_BATTERY, CLEAR, ENTER, BARCODE_DETECTED,
    etc.). These tend to have more "work" associated with their
    recognition than a set of equivalent symbols (e.g., digits).

    And, while each may be handled differently from the others,
    they tend to be handled the same way when encountered in
    different states. I.e., a POWER_FAIL is processed the same
    each place it is considered significant.

    Yes. And you could (at least theoretically) represent this in flex by encoding POWER_FAIL, etc. as characters or strings and sending those characters or strings as input to the reader when those events occur. Internal state transitions can be handled the same way: send
    characters to the reader.

    Again, this is an abuse of the tool. Just because you can do it does
    not mean you should do it.

    It would have appeal *if* it could perform reductions/optimizations
    that would otherwise have to be done by hand (or with another tool)

    I build "state subroutines" to handle sets of symbols that
    are handled the same way but "invoked" from different states
    (see Exceptions). But, the individual symbols can invoke
    different actions *and* different next states -- as long as
    they are consistent in each "application".

    flex (not lex) permits defining contextual "start" states, which the
    code can arbitrarily switch among. The same input can be treated
    differently in different start states. These really are coroutines -
    not subroutines - and the user code decides which state to switch to
    next, but flex does provides a stack so you can use them as
    subroutines (without having to track the nesting yourself).

    If instead you did something like:

    integer: [:digit:] return 'i'
    hex: [:digit:]|['a'-'f'] return 'h';

    This would blow up in your face because 0..9 would never be recognized
    a hex digit, but more importantly the 2 uses of the class lead
    /immediately/ to different actions so the class subroutine (subgraph)
    would have to be repeated in the FA with different exit actions.

    Yes. If the tool places an implicit priority on the rules
    based on the order in which they are encountered. I intentionally
    don't specify this in the design of the tables, leaving the
    "post processor" some latitude in how it implements them
    and the runtime some potential efficiencies.

    The tool places priority on the longest, most precise match. It falls
    back on definition order when the input - as given - matches multiple patterns.

    In a (hardware) FSM, one would see all of the "possible exits" from a particular state and could notice ambiguities:
    X*Y*/Z
    X*Y
    clearly overlap.

    Furthermore, one could detect these conflicts with a simple tool;
    it need not understand the entire machine, just look at a single state
    and the transitions leaving it.

    But again, start states can (sometimes) be used to get around this
    behavior.

    What's interesting (being a hardware-software person) is that, despite
    the obvious duality, the approaches taken to these technologies is so disjointed. DFA tend to use a parser-generator of preference while FSMs
    (in software) have a variety of different implementations with dramatic
    design and runtime differences in efficiencies.

    Similarly, that hardware FSMs tend to be designed with total disregard
    to the possible applicability of parser generators, regex compilers, etc.

    Its as if each domain has its own notion of how the technology should
    be applied and implemented.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Clifford Heath@21:1/5 to George Neuner on Mon Mar 27 16:18:51 2023
    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.


    The stack means it's not a FA. Yacc and bison exist for the sole purpose
    of processing LALR2 grammars that cannot be processed with an FA. Also
    because the grammars are LALR, the stack is a bottom-up stack, so it
    doesn't resemble anything you'll see in a top-down parser, and you'll
    get parse errors that probably don't really tell you what is wrong with
    the input :P. Lex/Flex on the other hand exists to process only finite
    states. The FSM algorithms they use are more efficient than any
    algorithm that can handle LALR2, which is why these tools still exist as independent tools.

    Notably, the combination of yacc&lex (or flex&bison) still isn't
    powerful enough even to parse C without extra help - goto labels blow
    thing up and there is a hand-coded hack in the C language lexers for it.

    ANTLR also implements some version of an LR/LALR parser, but instead of
    a finite 2 tokens lookahead, it transforms arbitrary lookahead
    expressions into something finite (an FSM), and if it can't do that, it
    fails. Terence Parr got his PhD for figuring out how to do that transformation... and lived to tell the tale. :)

    Anyone interested in the overlap between regular languages and finite
    state machines should refer to the excellent
    <https://github.com/katef/libfsm>. You can give it an assortment of
    regular expressions and it will unify them and construct a DFA to
    process them. The README at the top of that page has a simple example,
    and there's a tutorial if you want to look further. This library is
    perfectly at home processing arbitrary binary file formats and
    protocols, not just programming language text files. But only the parts
    that are reducible to a FA... Nevertheless there is absolutely nothing
    wrong with using this kind of library to write arbitrary FSMs.

    I'm currently building a generalised parsing engine that also has the capability of processing arbitrary binary file and network stream
    formats, using a VM approach that interprets something very like a BNF,
    but in prefix notation (+a means one-or-more "a"s, not a+). It's tiny, efficient, embeddable, but can take a protocol description in a very few
    bytes of VM code to handle almost any new protocol or format. I don't
    think that has been done before, and I've wanted to do it for 25 years.

    Clifford Heath.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Mon Mar 27 02:32:46 2023
    On Sun, 26 Mar 2023 03:35:27 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/25/2023 9:45 PM, George Neuner wrote:

    My hardware classes talked about FSMs, Meely/Moore, "state diagrams"
    and optimization techniques.

    My software classes talked about DFAs, EBNFs, *railroad* diagrams
    but never a mention of optimization tools or techniques.

    This I think is a teaching failure.


    Before we go on here we have to clarify a possible terminology trap: "deterministic" vs "non-deterministic".

    In the context of FA, "deterministic" means that the machine can be
    only in one state at any given time. "non-deterministic" means that
    the machine (at least logically) can simultaneously be in a set of
    multiple states.



    To explain this better, I'm falling back on lexing because it is
    simple minded. You will need to generalize the concepts to consider
    other possible uses.

    Ignoring the behavior of any real-world tools and just thinking about
    an *ideal* recognizer, consider

    integer: [:digit:]+
    hex : [:digit:]+|[a-fA-F]+

    Lacking further input, the sequence "1234" is ambiguous - the
    recognizer doesn't know yet whether it has an integer value or a hex
    value. Logically it must consider both patterns simultaneously, and
    so logically the recognizer must be an NDFA.

    For every NDFA there is a corresponding DFA which contains an equal or
    greater number of states. Where the NDFA logically would be in a set
    of states simultaneously, the corresponding DFA will contain not only
    those explicit NDFA states but also additional states which represent
    possible combinations of those states which the NDFA could find itself
    in. The additional states are required because a DFA can be in only
    one state at any given time, so it needs a way to (logically)
    represent being in multiple states simultaneously. The additional
    "set" states serve to disambiguate ambiguous state transitions ...
    eventually the DFA must arrive in one of the explicit states of the
    original NDFA.



    The typical notion of FSM as taught to hardware oriented students
    corresponds to non-deterministic FA. Hardware can directly implement
    an NDFA, but software can only *emulate* it - with all the caveats
    implied by emulation.

    Algorithms to transform graph based NDFA to DFA and back again have
    been known at least since the 1950s, as have ways of generating table
    driven vs switch based machines from a graph. But, typically, none of
    this ever is taught to hardware oriented students (or even most
    software oriented students) - if they learn anything at all, they
    learn some practical methods to manually achieve the same results.


    From the software viewpoint, you rarely, if ever, would try to design
    a DFA directly. Instead you would design an NDFA that does what you
    want, and then (for performance) you have it algorithmically
    transformed into its corresponding DFA form. The transformation
    [assuming it's done right ;-)] produces an optimal DFA state machine.



    (f)lex is a tool that can - at least technically - create general
    state machines. However, because it was designed for string
    recognition, its machine description language is specialized for that
    use.

    yacc and bison don't even try to create general state machines - they
    create a very specific type of FA which is optimized for parsing. And
    again, because they were designed for parsing, their machine
    description languages are specialized for that task.

    UML tools are what you need to consider for more general FA / FSM.



    They also seem to be applied differently. E.g., in a (hardware) FSM,
    it is not uncommon to list a logical expression as the stimulus
    for a transition (e.g., "LineFeed & /Last_Line" vs. "LineFeed & LastLine" >directing the machine to two different states with two different outputs
    or actions). In DFAs, it was always just sequences of symbols -- the
    sorts of things that would specify a grammar (inherently serial, one-at-a-time >"conditions").

    FSM *are* FA are just alternate terms for the same concept.

    There is nothing whatsoever which limits one or the other to any
    particular uses. Any apparent difference is an artifact of how they
    are taught to students in different disciplines: hardware students
    learn practice but rarely, if ever, learn the theory.

    And, in truth, only CS students taking language / compiler courses
    ever will learn how to build NDFA and DFA state graphs, convert one
    graph form into the other, or how to generate table driven or switch
    code from a state graph.



    Purely as a techical matter, (f)lex can create general FA assuming
    that transition conditions can be represented as character input to
    the reader. The "reader" function is completely redefineable: the
    default is to read from STDIN, but, in fact, a custom reader can do
    absolutely anything under the hood so long as it returns a character
    (or EOF) when called.

    Therein lies a notable limitation. In a (hardware) FSM, there are no limits >to the number of inputs that can CONCURRENTLY be examined by the machine. >E.g., I could label a transition with:
    A*/B*/C*D*E*F*/G*H*I*J*/K*/L*/M + N*O*P*Q + /R*/S*/T*U*V + W + X*Y*Z
    To represent this to lex/yacc, I would have to reduce it to a "narrow"
    symbol -- possible if there are only a limited number of such combinations
    in the grammar (as sourced by the lexer).

    You could just use the string above to represent the condition.

    But this is where (f)lex falls down hard: you would have to define
    strings that represent all possible combinations of your simultaneous conditions, and to drive the resulting DFA the code that monitors your
    hardware must be able to send those condition strings into the
    recognizer.

    If you can do that, (f)lex will happily generate a working state
    machine for you.

    In practice you would not want to do this. A decent UML tool would be
    a much better choice.



    In a (hardware) FSM, one would see all of the "possible exits" from a >particular state and could notice ambiguities:
    X*Y*/Z
    X*Y
    clearly overlap.

    Furthermore, one could detect these conflicts with a simple tool;
    it need not understand the entire machine, just look at a single state
    and the transitions leaving it.

    That's why you need a tool designed for the purpose. All of our
    discussion here about what is possible with (f)lex is academic ...
    nobody in their right mind should be doing it.



    What's interesting (being a hardware-software person) is that, despite
    the obvious duality, the approaches taken to these technologies is so >disjointed. DFA tend to use a parser-generator of preference while FSMs
    (in software) have a variety of different implementations with dramatic >design and runtime differences in efficiencies.

    Similarly, that hardware FSMs tend to be designed with total disregard
    to the possible applicability of parser generators, regex compilers, etc.

    Its as if each domain has its own notion of how the technology should
    be applied and implemented.

    Unfortunately yes. I think very few people ever think about it enough
    to recognize that.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Mon Mar 27 01:16:17 2023
    On 3/26/2023 11:32 PM, George Neuner wrote:
    On Sun, 26 Mar 2023 03:35:27 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:

    On 3/25/2023 9:45 PM, George Neuner wrote:

    My hardware classes talked about FSMs, Meely/Moore, "state diagrams"
    and optimization techniques.

    My software classes talked about DFAs, EBNFs, *railroad* diagrams
    but never a mention of optimization tools or techniques.

    This I think is a teaching failure.

    I agree, and disagree.

    Where do you draw the line between disciplines? With hardware,
    you're exposed to lots of tools for logic reduction, etc. You
    learn about hazzards, races, etc. These aren't mentioned in
    software contexts -- but still exist (perhaps even moreso
    as software *isn't* a set of concurrent actions; possibly
    why so many software people have difficulty "thinking in
    parallel"?).

    The curriculum I was exposed to was a mixture of hardware courses
    and software courses. People following a "pure EE" degree
    took the same hardware courses as I *and* some of the "core"
    software courses that I did. OTOH, folks pursuing the CS
    option skipped some of the more "advanced" hardware courses
    and, instead, took the more advanced *software* courses -- which
    were devoid of any "pure EE" students.

    Should these overlapping courses have been taught with more
    of a multi-discipline emphasis? Should the instructors have
    conditioned each presentation with "for you CS students..."
    and "for you EE students..."?

    Or, should they have expected the students to be aware enough to
    recognize these dualities??

    Before we go on here we have to clarify a possible terminology trap: "deterministic" vs "non-deterministic".

    In the context of FA, "deterministic" means that the machine can be
    only in one state at any given time. "non-deterministic" means that
    the machine (at least logically) can simultaneously be in a set of
    multiple states.

    Yes ("logically" -- virtually? -- being the key concept, there)

    To explain this better, I'm falling back on lexing because it is
    simple minded. You will need to generalize the concepts to consider
    other possible uses.

    Ignoring the behavior of any real-world tools and just thinking about
    an *ideal* recognizer, consider

    integer: [:digit:]+
    hex : [:digit:]+|[a-fA-F]+

    Lacking further input, the sequence "1234" is ambiguous - the
    recognizer doesn't know yet whether it has an integer value or a hex
    value. Logically it must consider both patterns simultaneously, and
    so logically the recognizer must be an NDFA.

    For every NDFA there is a corresponding DFA which contains an equal or greater number of states. Where the NDFA logically would be in a set
    of states simultaneously, the corresponding DFA will contain not only
    those explicit NDFA states but also additional states which represent possible combinations of those states which the NDFA could find itself
    in. The additional states are required because a DFA can be in only
    one state at any given time, so it needs a way to (logically)
    represent being in multiple states simultaneously. The additional
    "set" states serve to disambiguate ambiguous state transitions ...
    eventually the DFA must arrive in one of the explicit states of the
    original NDFA.

    The typical notion of FSM as taught to hardware oriented students
    corresponds to non-deterministic FA. Hardware can directly implement
    an NDFA, but software can only *emulate* it - with all the caveats
    implied by emulation.

    Here being where the "virtually" comes into play.

    The hardware machine *is* only in a single ACTUAL state at any given
    time (because there is just one set of state variables and that tuple
    defines THE state). Until an [a..f] is encountered, it is content
    being in that single state.

    However, once one is encountered, it has to recognize that it
    actually is in one *particular* variant of that state (assuming
    that "hex" and "integer" can have different contexts, elsewhere
    in the grammar)

    Algorithms to transform graph based NDFA to DFA and back again have
    been known at least since the 1950s, as have ways of generating table
    driven vs switch based machines from a graph. But, typically, none of
    this ever is taught to hardware oriented students (or even most
    software oriented students) - if they learn anything at all, they
    learn some practical methods to manually achieve the same results.

    From the software viewpoint, you rarely, if ever, would try to design
    a DFA directly. Instead you would design an NDFA that does what you
    want, and then (for performance) you have it algorithmically
    transformed into its corresponding DFA form. The transformation
    [assuming it's done right ;-)] produces an optimal DFA state machine.

    (f)lex is a tool that can - at least technically - create general
    state machines. However, because it was designed for string
    recognition, its machine description language is specialized for that
    use.

    Exactly.

    yacc and bison don't even try to create general state machines - they
    create a very specific type of FA which is optimized for parsing. And
    again, because they were designed for parsing, their machine
    description languages are specialized for that task.

    UML tools are what you need to consider for more general FA / FSM.

    Which brings us full circle to the top of the thread.
    I contend that to be expressive enough (i.e., to acts AS
    equivalents for) to generate code, such a notation would
    be just as complex as writing that code.

    And, given that one *must* write code -- but needn't always
    reduce a design to an FSM -- you end up developing a second tool
    that the developer is reliant upon but with less "practice"
    than that of writing code.

    They also seem to be applied differently. E.g., in a (hardware) FSM,
    it is not uncommon to list a logical expression as the stimulus
    for a transition (e.g., "LineFeed & /Last_Line" vs. "LineFeed & LastLine"
    directing the machine to two different states with two different outputs
    or actions). In DFAs, it was always just sequences of symbols -- the
    sorts of things that would specify a grammar (inherently serial, one-at-a-time
    "conditions").

    FSM *are* FA are just alternate terms for the same concept.

    There is nothing whatsoever which limits one or the other to any
    particular uses. Any apparent difference is an artifact of how they
    are taught to students in different disciplines: hardware students
    learn practice but rarely, if ever, learn the theory.

    In hardware designs, you can directly see the costs of an
    implementation: how many FFs to represent the state,
    how much combinatorial logic to determine next_state and
    outputs, etc. So, optimization (can) results in a savings
    of circuitry. And, can speed up the machine by eliminating
    a serial "step".

    [E.g., in the state diagram I posted, one could add a
    FINISHED state at the "bottom" of each sequence of states
    in the different paths through the graph. But, this would
    just add an extra clock cycle before the machine could
    return to the *top* of the graph and start the next sequence]

    And, because hardware folks *do* think in parallel, they
    see solutions in a different way than serial-thinking
    software types.

    E.g., if you wanted to know how much data was in a FIFO,
    you'd subtract tail_pointer from head_pointer (neglecting
    wrap) to get a result.

    But, this takes an operation invoked AFTER head and tail
    are stable.

    A hardware person would move that computation in parallel
    with the head and tail update actions. E.g., at reset,
    head=tail, amount=0. Each time head is advanced, increase
    amount SIMULTANEOUSLY. Each time tail is advanced, decrease
    amount simultaneously. When both head and tail advance
    at the same time, amount remains unchanged.

    Because this "difference" was moved up in time, the
    circuit can run faster -- you don't have to wait for the
    values of the head and tail pointers to "settle" and
    THEN propagate through the differencing logic; that was
    already done WHILE the pointers were being updated!

    The software person could adopt a similar strategy,
    but it doesn't *save* anything because the "amount"
    still has to be processed in a separate instruction
    cycle -- either increment/decrement/do-nothing *or*
    compute head-tail.

    And, in truth, only CS students taking language / compiler courses
    ever will learn how to build NDFA and DFA state graphs, convert one
    graph form into the other, or how to generate table driven or switch
    code from a state graph.

    My education is dated in that *all* CS students learned how to design
    grammars, build compilers, etc. when I was taught. Now, I suspect
    "CS" means "programmer".

    Purely as a techical matter, (f)lex can create general FA assuming
    that transition conditions can be represented as character input to
    the reader. The "reader" function is completely redefineable: the
    default is to read from STDIN, but, in fact, a custom reader can do
    absolutely anything under the hood so long as it returns a character
    (or EOF) when called.

    Therein lies a notable limitation. In a (hardware) FSM, there are no limits >> to the number of inputs that can CONCURRENTLY be examined by the machine.
    E.g., I could label a transition with:
    A*/B*/C*D*E*F*/G*H*I*J*/K*/L*/M + N*O*P*Q + /R*/S*/T*U*V + W + X*Y*Z
    To represent this to lex/yacc, I would have to reduce it to a "narrow"
    symbol -- possible if there are only a limited number of such combinations >> in the grammar (as sourced by the lexer).

    You could just use the string above to represent the condition.

    But, you would also have to recognize
    /M*/L*/K*J*I*H*/G*F*E*D*/C*/B*A + N*O*P*Q + /R*/S*/T*U*V + W + X*Y*Z
    (and a shitload of other alternatives) as being equivalent strings!

    But this is where (f)lex falls down hard: you would have to define
    strings that represent all possible combinations of your simultaneous conditions, and to drive the resulting DFA the code that monitors your hardware must be able to send those condition strings into the
    recognizer.

    Exactly. In a hardware design, I can choose to show only the
    conditions/inputs that are of interest to me and in any way
    that "fits best on the paper" -- because they are reified
    in parallel.

    If you can do that, (f)lex will happily generate a working state
    machine for you.

    But, it still won't recognize that "FINISHED" and "READY" are
    equivalent states!

    In practice you would not want to do this. A decent UML tool would be
    a much better choice.

    In a (hardware) FSM, one would see all of the "possible exits" from a
    particular state and could notice ambiguities:
    X*Y*/Z
    X*Y
    clearly overlap.

    Furthermore, one could detect these conflicts with a simple tool;
    it need not understand the entire machine, just look at a single state
    and the transitions leaving it.

    That's why you need a tool designed for the purpose. All of our
    discussion here about what is possible with (f)lex is academic ...
    nobody in their right mind should be doing it.

    Unless the machine in question was simple enough.

    What's interesting (being a hardware-software person) is that, despite
    the obvious duality, the approaches taken to these technologies is so
    disjointed. DFA tend to use a parser-generator of preference while FSMs
    (in software) have a variety of different implementations with dramatic
    design and runtime differences in efficiencies.

    Similarly, that hardware FSMs tend to be designed with total disregard
    to the possible applicability of parser generators, regex compilers, etc.

    Its as if each domain has its own notion of how the technology should
    be applied and implemented.

    Unfortunately yes. I think very few people ever think about it enough
    to recognize that.

    Because they likely don't work in both domains.

    Think about it; as a hardware person, I see nothing different between:
    ready * /buffer_full
    and
    /(/ready + buffer_full)
    I could draw either representation schematically and recognize that
    the same gates were involved. I would choose the "expression"
    (rendition) that best "fit into" what *followed* that "signal".

    For software people, this seems to require a conscious effort
    ("What are the equivalent ways of expressing this and which
    makes most sense to someone reading my code, later?") so you
    often see expressions that you have to THINK about instead of
    being more intuitively expressed.

    Likewise, a hardware person KNOWS that changing multiple
    signals "concurrently" can lead to races and hazards.
    But, a software person has to be lectured in atomic operators
    (because time is serial to him -- ASSUMING he thinks about it!).

    Folks taught in (just) one domain often are poor practitioners
    in the other.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to no.spam@please.net on Tue Mar 28 11:17:37 2023
    On Mon, 27 Mar 2023 16:18:51 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a
    particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.


    The stack means it's not a FA.

    No, it still is an FA ... it just is a specialized form.

    Yacc and bison exist for the sole purpose
    of processing LALR2 grammars that cannot be processed with an FA.

    You mean LALR(1) in the case of yacc, and LR(1) in the case of bison.
    LALR is a restricted case of LR, it does /not/ mean LR with lookahead.
    Neither tool can handle 2 tokens of lookahead.

    Stackless FA, in fact, can process LR(1) grammars ... they just need
    (typically many) more states in the machine to do so. The stack FA
    was created specifically to reduce the memory footprint of the parser
    - necessary in ~1970, but generally much less of a concern now.


    Also
    because the grammars are LALR, the stack is a bottom-up stack, so it
    doesn't resemble anything you'll see in a top-down parser, ...

    True.

    ... and you'll
    get parse errors that probably don't really tell you what is wrong with
    the input :P.

    You can't rely on the tool for error handling (or even just messages)
    ... you really need to add deliberate error handling.


    Lex/Flex on the other hand exists to process only finite
    states. The FSM algorithms they use are more efficient than any
    algorithm that can handle LALR2, which is why these tools still exist as >independent tools.

    They exist separately because they were intended for different tasks
    AND because they needed a considerable (at the time) amount of memory
    to analyze the input spec and generate a recognizer for it.

    In fact, regex tools existed already for a number of years before
    either lex or yacc came about. The difference was most previous tools
    directly /interpreted/ regex patterns, and typically tried them one at
    a time under host program control (e.g., see RE2C), whereas lex
    compiled multiple patterns into a single recognizer that (effectively)
    tried all patterns simultaneously. This made lex recognizers much
    faster (though larger) and far more efficient for handling large
    numbers of patterns [such as in a language compiler].


    Notably, the combination of yacc&lex (or flex&bison) still isn't
    powerful enough even to parse C without extra help - goto labels blow
    thing up and there is a hand-coded hack in the C language lexers for it.

    ANTLR also implements some version of an LR/LALR parser ...

    ANTLR implements LL(*) which is LL with unbounded lookahead. There
    are other LL(k) tools which require the programmer to choose a fixed
    amount of lookahead (and fail to process the grammar if the k value is
    too small). ANTLR analyzes the grammar and computes what lookahead is
    required pattern by pattern.


    ... but instead of
    a finite 2 tokens lookahead, it transforms arbitrary lookahead
    expressions into something finite (an FSM), and if it can't do that, it >fails. Terence Parr got his PhD for figuring out how to do that >transformation... and lived to tell the tale. :)

    Almost: substitute variable k for 2. And again, ANTLR is LL.


    Anyone interested in the overlap between regular languages and finite
    state machines should refer to the excellent ><https://github.com/katef/libfsm>. You can give it an assortment of
    regular expressions and it will unify them and construct a DFA to
    process them. The README at the top of that page has a simple example,
    and there's a tutorial if you want to look further. This library is
    perfectly at home processing arbitrary binary file formats and
    protocols, not just programming language text files. But only the parts
    that are reducible to a FA... Nevertheless there is absolutely nothing
    wrong with using this kind of library to write arbitrary FSMs.

    I'm currently building a generalised parsing engine that also has the >capability of processing arbitrary binary file and network stream
    formats, using a VM approach that interprets something very like a BNF,
    but in prefix notation (+a means one-or-more "a"s, not a+). It's tiny, >efficient, embeddable, but can take a protocol description in a very few >bytes of VM code to handle almost any new protocol or format. I don't
    think that has been done before, and I've wanted to do it for 25 years.

    I would be interested to see that (when it's finished, of course).
    Good luck!


    Clifford Heath.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to blockedofcourse@foo.invalid on Tue Mar 28 15:25:44 2023
    On Mon, 27 Mar 2023 01:16:17 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    On 3/26/2023 11:32 PM, George Neuner wrote:

    The hardware machine *is* only in a single ACTUAL state at any given
    time (because there is just one set of state variables and that tuple
    defines THE state). Until an [a..f] is encountered, it is content
    being in that single state.

    The hardware is in a single state, but that state simultaneously
    reflects the current value (for lack of better term) of potentially
    many variables / signals.

    The same is true of software DFA. The major difference wrt hardware is
    in that transition between states can be based on only 1 logical
    condition. That one logical condition may, in fact, be a
    conglomeration of any number of things, but so far as the /machine/ is concerned, it manifests as one unique input. Therefore every possible (combination of things) condition which might cause a state transition
    has to be enumerated separately.

    Software NDFA trade processing speed for (sometimes lots of) state
    memory. NDFA have no more capability than DFA, but they can do the
    same job with fewer explicit machine states, because a single state in
    NDFA can represent multiple states of the corresponding DFA. The
    tradeoff is that transition conditions in NDFA often are much more
    complex than in DFA, and thus evaluating them takes more time and
    effort.


    However, once one is encountered, it has to recognize that it
    actually is in one *particular* variant of that state (assuming
    that "hex" and "integer" can have different contexts, elsewhere
    in the grammar)

    That is why hardware is a better analogy to NDFA, where many (or all)
    of the variants may be represented by a single machine state. In DFA,
    there would need to be a separate state for each variant.


    UML tools are what you need to consider for more general FA / FSM.

    Which brings us full circle to the top of the thread.
    I contend that to be expressive enough (i.e., to acts AS
    equivalents for) to generate code, such a notation would
    be just as complex as writing that code.

    And, given that one *must* write code -- but needn't always
    reduce a design to an FSM -- you end up developing a second tool
    that the developer is reliant upon but with less "practice"
    than that of writing code.

    Agree and disagree.

    YMMV, but a lot of hand written state machines I have seen over the
    years included a lot of duplicated condition / transition decision
    code that could have been simplified or eliminated by the introdution
    of additional explicit states.

    Reminded of the proverb: "programmers are great at figuring out what
    CAN be in parallel, but not what SHOULD be done in parallel".

    A tool can aid in figuring out what states are necessary, given the
    conditions, to create an optimal (software) machine.


    In hardware designs, you can directly see the costs of an
    implementation: how many FFs to represent the state,
    how much combinatorial logic to determine next_state and
    outputs, etc. So, optimization (can) results in a savings
    of circuitry. And, can speed up the machine by eliminating
    a serial "step".

    You can think of a state in a software DFA as being analogous to a
    1-bit latch. Effectively all you need to know is whether or not the
    state currently is active.

    State transitions don't have a simple mapping to hardware as they can
    be (essentially) unrestricted logical expressions. Evaluation needs
    at least a simple ALU (full set of logic ops), an accumulator, and a
    (per combination) latch to store the result.
    [If the signals all are simple on/off the accumulator and the latches
    maybe all could be 1-bit.]


    And, in truth, only CS students taking language / compiler courses
    ever will learn how to build NDFA and DFA state graphs, convert one
    graph form into the other, or how to generate table driven or switch
    code from a state graph.

    My education is dated in that *all* CS students learned how to design >grammars, build compilers, etc. when I was taught. Now, I suspect
    "CS" means "programmer".

    No. CS students learn theory. CSE and maybe also IS students learn
    about development toolchains.

    This dichotemy between theory and practice has existed at least since
    the 80's (when I was in college) and probably started even earlier.
    Prior to ~ late 90s, explicit CSE degrees didn't exist - there were
    just certificate programming courses (if applicable), and the project management aspects had to be learned on the job.

    CSEs really are just "developers with a degree".


    What's interesting (being a hardware-software person) is that, despite
    the obvious duality, the approaches taken to these technologies is so
    disjointed. DFA tend to use a parser-generator of preference while FSMs >>> (in software) have a variety of different implementations with dramatic
    design and runtime differences in efficiencies.

    Similarly, that hardware FSMs tend to be designed with total disregard
    to the possible applicability of parser generators, regex compilers, etc. >>>
    Its as if each domain has its own notion of how the technology should
    be applied and implemented.

    Unfortunately yes. I think very few people ever think about it enough
    to recognize that.

    Because they likely don't work in both domains.

    Think about it; as a hardware person, I see nothing different between:
    ready * /buffer_full
    and
    /(/ready + buffer_full)
    I could draw either representation schematically and recognize that
    the same gates were involved. I would choose the "expression"
    (rendition) that best "fit into" what *followed* that "signal".

    For software people, this seems to require a conscious effort
    ("What are the equivalent ways of expressing this and which
    makes most sense to someone reading my code, later?") so you
    often see expressions that you have to THINK about instead of
    being more intuitively expressed.

    I'm primarily a software person, though I have done simple (mostly
    TTL) interface hardware, and some not so simple FPGA programming [but
    that I think still counts as "software"]. I have done a lot of
    bit-banging and bare hardware programming.

    I think the problem really is that too many programmers now do NOT
    ever learn assembler. I had learned a few different assembler
    languages before I learned C, and I think it helped immensely because
    I never had any trouble with pointers or indirections, etc., or
    manually managing memory ... the very things that tend to confound C
    newbies.


    Likewise, a hardware person KNOWS that changing multiple
    signals "concurrently" can lead to races and hazards.
    But, a software person has to be lectured in atomic operators
    (because time is serial to him -- ASSUMING he thinks about it!).

    Too much specialization in education.

    Concurrency, parallelism and atomic operations tend to be addressed
    (not "taught" per se) only in OS classes. Many CS students do not
    take OS classes. Atomics and threading are covered in CSE, but only
    the practical uses of them and not the theory (or how they evolved
    which I think is almost as important).


    Folks taught in (just) one domain often are poor practitioners
    in the other.

    The software industry, in particular, now tends to frown upon
    generalists for developer positions, and for management any prior
    developer experience no longer much matters.

    If you can't demonstrate significant expertise in ___ of the week, in
    most places you won't even make it past HR to be interviewed by the
    people who can recognize that your prior experience has relevance and
    that you could quickly learn whatever is needed to do the job.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Clifford Heath@21:1/5 to George Neuner on Wed Mar 29 09:00:34 2023
    On 29/03/23 02:17, George Neuner wrote:
    On Mon, 27 Mar 2023 16:18:51 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a
    particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.

    The stack means it's not a FA.

    No, it still is an FA ... it just is a specialized form.

    Ok, it's an FA operating on a stack. The stack makes the whole thing non-regular, aka infinite, so it's only an FA if you exclude the stack
    from the machine.

    Stackless FA, in fact, can process LR(1) grammars ... they just need (typically many) more states in the machine to do so


    No. A stack is not finite. Every FA is finite, that's why they're called
    FA. If you want to process a regular language, you can use an FA. If you
    want to process an irregular language, you cannot - you need somewhere
    to store unbounded staes and an FA *cannot* do that. It's in the
    definition of such things!

    ... and you'll
    get parse errors that probably don't really tell you what is wrong with
    the input :P.

    You can't rely on the tool for error handling (or even just messages)
    ... you really need to add deliberate error handling.

    I wasn't talking about error recovery, just about reporting. Both are
    hugely easier in an LL grammar. In the PEG parsers that I favour, you
    can almost always just report the rules on the stack at the furthest
    point reached, and (in all the grammars I've implemented) that gives a
    better error report than anything you'd bother to create manually.

    It amuses me that the folk who understand grammar well enough to be able
    to produce powerful parser generators seem to be universally incapable
    of generating code that can report parse failures in plain language.
    Something about their brain's language centres has become so esoteric
    that normal language escapes them.


    Lex/Flex on the other hand exists to process only finite
    states. The FSM algorithms they use are more efficient than any
    algorithm that can handle LALR2, which is why these tools still exist as
    independent tools.

    They exist separately because they were intended for different tasks

    The articles published at the time I first used them (in 1980) clearly
    stated that the two tools were needed "because we don't have a single
    algorithm that is equally efficient at both tokenisation and parsing".

    That's the entire reason that there *are* two separate tasks.
    The same justification existed for the existence of egrep vs grep, BTW.

    I tried to find the actual text, but it eludes me.

    In a PEG parser, both tasks are equally efficient, and they are combined
    into one grammar, so there aren't two tasks any more.

    In fact, regex tools existed already for a number of years before
    either lex or yacc came about. The difference was most previous tools directly /interpreted/ regex patterns,

    Ken Thompson's implementation in the mid 1960s (documented in a 1968
    CACM paper) translated the regexp into machine code. The list of
    possible states was just a sequence of function call instructions.

    The technique of converting multiple NFAs into a single DFA has also
    been in use since the early 70s.

    Have a read of Russ Cox's excellent presentation of these topics here: <https://swtch.com/~rsc/regexp/regexp1.html>.

    I implemented Thompson's algorithm here before deprecating it for PEGs: <https://github.com/cjheath/strpp/blob/main/include/strregex.h>

    It's baffling that many major languages *still* implement Regex using
    the incredibly inferior backtracking approach.

    whereas lex
    compiled multiple patterns into a single recognizer that (effectively)
    tried all patterns simultaneously.

    Exactly, that's the NFA* -> DFA thing I talked about.

    ANTLR implements LL(*) which is LL with unbounded lookahead.


    It's unbounded, but must be regular. Many languages (including my
    Constellation Query Language) require unbounded non-regular look-ahead,
    which PEG provides, at some extra cost in memory. But the pathological
    cases which *require* memoization only occur rarely, so a global packrat strategy is sub-optimal.

    There
    are other LL(k) tools which require the programmer to choose a fixed
    amount of lookahead (and fail to process the grammar if the k value is
    too small). ANTLR analyzes the grammar and computes what lookahead is required pattern by pattern.

    That's a poor description of how it works. It looks ahead using an FA,
    so lookahead must be regular ("Finite State").

    Anyone interested in the overlap between regular languages and finite
    state machines should refer to the excellent
    <https://github.com/katef/libfsm>.

    Did you look at the FSM on the main README page of that site?
    It shows two RE's being combined into one DFA. Very neat stuff.

    I'm currently building a generalised parsing engine that also has the
    capability of processing arbitrary binary file and network stream
    formats, using a VM approach that interprets something very like a BNF,
    but in prefix notation (+a means one-or-more "a"s, not a+). It's tiny,
    efficient, embeddable, but can take a protocol description in a very few
    bytes of VM code to handle almost any new protocol or format. I don't
    think that has been done before, and I've wanted to do it for 25 years.

    I would be interested to see that (when it's finished, of course).
    Good luck!

    The putative grammar for Px is here (but this doesn't describe captures
    fully):
    <https://github.com/cjheath/strpp/blob/main/grammars/px.px>

    and the Pegexp engine is here (a template that I'm specialising to add non-regular aka full LL grammar capability): <https://github.com/cjheath/strpp/blob/main/include/pegexp.h>

    The Px grammar rewritten as a named-map of Pegexp expressions is here: <https://github.com/cjheath/strpp/blob/main/test/peg_test.cpp#L55-L91>
    but I'll use a better structure for a compiled Px grammar, so that names
    don't need to be looked up at runtime.

    I've almost finished dicking with the structure of input streams that
    will make it feasible for this to process data directly arriving on a
    socket, and only caching as much as is needed for back-up and retry.
    It's also possible to compile with/without UTF-8 support, but I can make
    that more convenient. It's possible to specify binary matching even in a Unicode parser though.

    I want captures to do things like turn the ASCII digits on an HTTP Content-Length header into a binary integer, save that integer as a
    capture variable, and use that variable to count bytes in a later
    repetition. This will enable a simple grammar describe all of HTTP/2.

    By nesting parsers (incrementally feeding capture sections to a nested
    parser) it should be possible to for example, run a protocol engine that generates an HTTP/2 request (generating from an HTTP request grammar),
    parses the response chunks, feeds base64-encoded chunks into a
    conversion function (not specified in Px), and the output of that
    conversion into e.g. a JPEG parser that actually verifies the JPEG
    format, and can e.g. extract (as a parse capture) the GPS location from
    inside the Exif data attached... and all without having to extend or
    recompile the engine. Just load the target grammar, and if it succeeds,
    you get the GPS location... and all file formats have been validated.

    I envisage a world where the file-system is type-safe; almost no file is
    a pure byte-stream, and it's not possible to save a JPEG file that
    doesn't match the JPEG syntax. The file system must be pre-loaded with a grammar for every new file type before writing such a file.

    Clifford Heath.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Don Y@21:1/5 to George Neuner on Tue Mar 28 16:56:03 2023
    "Progress?" (Y/N)

    On 3/28/2023 12:25 PM, George Neuner wrote:

    UML tools are what you need to consider for more general FA / FSM.

    Which brings us full circle to the top of the thread.
    I contend that to be expressive enough (i.e., to acts AS
    equivalents for) to generate code, such a notation would
    be just as complex as writing that code.

    And, given that one *must* write code -- but needn't always
    reduce a design to an FSM -- you end up developing a second tool
    that the developer is reliant upon but with less "practice"
    than that of writing code.

    Agree and disagree.

    YMMV, but a lot of hand written state machines I have seen over the
    years included a lot of duplicated condition / transition decision
    code that could have been simplified or eliminated by the introdution
    of additional explicit states.

    I think that is a consequence of the design approach. It is not
    uncommon (an understatement?) for software to be developed
    incrementally; build part of the machine, build a bit more,
    and more, ... done.

    This just doesn't work in hardware. You'd have to effectively
    discard all of your previous work each time you "add a bit more".

    As a result, you think about the entire machine before you settle
    on an architecture or even begin the implementation.

    Imagine designing a processor. You need to have an idea as to what
    the entire instruction set is likely going to be before you can
    figure out what inputs the control store needs to be able to
    examine.

    Software, OTOH, can always squeeze in a tweek to an existing
    design. It's only when you're "done" that you can (*might*)
    step back and look at your result -- and risk a refactoring.

    Reminded of the proverb: "programmers are great at figuring out what
    CAN be in parallel, but not what SHOULD be done in parallel".

    A tool can aid in figuring out what states are necessary, given the conditions, to create an optimal (software) machine.

    This can have an advantage with incremental design. But, again,
    means the developer has to be "fluent" in the tool as well
    as the implementation language(s), etc.

    And, in truth, only CS students taking language / compiler courses
    ever will learn how to build NDFA and DFA state graphs, convert one
    graph form into the other, or how to generate table driven or switch
    code from a state graph.

    My education is dated in that *all* CS students learned how to design
    grammars, build compilers, etc. when I was taught. Now, I suspect
    "CS" means "programmer".

    No. CS students learn theory. CSE and maybe also IS students learn
    about development toolchains.

    We learned of none of that. The theory being that it was too fluid
    and dependent on the your actual career.

    E.g., EVERY CS course used a different language, operating system/environment, etc. None of this was considered important to the material being presented. Just an annoying "implementation" detail.

    There was no mention of MPUs (MCUs and SoCs not existing back then), hardware interfaces, etc. You didn't "count bytes" or microseconds but, rather, dealt with all resources just as "big O". More "implementation details".

    [A similar approach was taken with *hardware*. Learn the concepts
    and "how to learn" and worry about the implementation details once
    you're on the job -- whatever that might be.]

    This dichotemy between theory and practice has existed at least since
    the 80's (when I was in college) and probably started even earlier.
    Prior to ~ late 90s, explicit CSE degrees didn't exist - there were
    just certificate programming courses (if applicable), and the project management aspects had to be learned on the job.

    Ah, project management wasn't taught, at all! Nor the economics
    associated with design. More implementation details. (This being
    the biggest shortcoming, IMO, in my education. What value all
    the theory if it's not economically feasible to use it? OTOH,
    why limit the education to those things that are feasible *today*
    and compromise the education for *tomorrow*?(

    For software people, this seems to require a conscious effort
    ("What are the equivalent ways of expressing this and which
    makes most sense to someone reading my code, later?") so you
    often see expressions that you have to THINK about instead of
    being more intuitively expressed.

    I'm primarily a software person, though I have done simple (mostly
    TTL) interface hardware, and some not so simple FPGA programming [but
    that I think still counts as "software"]. I have done a lot of
    bit-banging and bare hardware programming.

    I think the problem really is that too many programmers now do NOT
    ever learn assembler. I had learned a few different assembler
    languages before I learned C, and I think it helped immensely because
    I never had any trouble with pointers or indirections, etc., or
    manually managing memory ... the very things that tend to confound C
    newbies.

    Yes! In my case, my interest in hardware (I thought "computer science"
    was going to teach me to design *computers*) led me to select more
    "elective" courses that concentrated on those aspects of designs.
    So, when a language concept was presented, I could visualize what
    the hardware had to do to make it happen. E.g., I think of
    a pointer to an "array" as "&array[0]." as that's just what's going
    through my mind as I write the reference.

    It also facilitated my design of the instruction sets for my CPUs
    as I could think of what the *software* would want to do and
    how I could design the processor to facilitate those activities.

    [I keep looking for my notes on the various (hypothetical)
    "machines" that we discussed and the consequences for
    information hiding, parameter passing, etc. Back then,
    they were just "arbitrary letters" (e.g., S-machine)
    intended to illustrate different concepts. And, how different
    languages would rely on them]

    Likewise, a hardware person KNOWS that changing multiple
    signals "concurrently" can lead to races and hazards.
    But, a software person has to be lectured in atomic operators
    (because time is serial to him -- ASSUMING he thinks about it!).

    Too much specialization in education.

    Concurrency, parallelism and atomic operations tend to be addressed
    (not "taught" per se) only in OS classes. Many CS students do not
    take OS classes. Atomics and threading are covered in CSE, but only
    the practical uses of them and not the theory (or how they evolved
    which I think is almost as important).

    Again, all of this was part of the "CS" curriculum, "back then". But,
    always as abstractions. Petri nets, etc. No need to deal with an
    actual implementation because the "4 years" of an education would
    lead to a very different set of implementations between when the
    course was taught and the material applied!

    Folks taught in (just) one domain often are poor practitioners
    in the other.

    The software industry, in particular, now tends to frown upon
    generalists for developer positions, and for management any prior
    developer experience no longer much matters.

    Did it ever? :> ---------------^^^^^^^^^^^^ Think: "Peter Principle"

    If you can't demonstrate significant expertise in ___ of the week, in
    most places you won't even make it past HR to be interviewed by the
    people who can recognize that your prior experience has relevance and
    that you could quickly learn whatever is needed to do the job.

    Yes. I see friends who have "checklists" of specific skillsets
    (i.e., familiar with product/platform X) as the first level
    sort for candidates. I think the feeling is that they can't
    afford to wait for you to get up to speed on platform X and
    likely won't invest much in you when product Y comes along
    (fish or cut bait!).

    This has been the opposite of my experiences. The application
    domain is *so* broad that you can't realistically expect someone to
    have experience with some arbitrary X that is your bread-and-butter.
    So, you want someone who has demonstrated an ability to work in
    a variety of application domains, price points, etc. as reassurance
    that they can learn your constraints.

    [How many people have designed tablet press instrumentation?
    Or, marine autopilots? Or video game hardware? Or, marking
    engines? Or... If you set those as preconditions for
    job openings, you end up with few-to-none applicants!]

    A variety of experiences also tends to be indicative of folks
    who enjoy learning -- instead of just refining a given set of
    skills indefinitely. How much do you learn designing version Y
    (Y >> X) of the same product?

    This is where a software background pays off as it is more
    pliable. Look at how little hardware has changed over the
    decades (in terms of implementation technologies). When
    I started out, we had DTL, RTL, TTL, HiNIL, ECL, CMOS,
    NMOS, MNOS, etc. logic families. Flatpacks, DIPs/CERDIPs,
    UVEPROMs, OTP EPROMs, masked ROMs, bipolar RAM, CMOS
    RAM, DRAM, PALs, etc.

    Now, we just refine existing technologies -- endlessly (DDR5??)

    There are relatively few people creating the "interesting"
    devices (e.g., a PC-on-a-chip) and most of that effort goes
    to facilitate more advanced *software* designs! (when I
    started out, the idea that I'd be *using* virtual memory
    in a deeply embedded product was fantasy -- "Oh, look!
    This new generation of processors has *8* bits!!! What
    a step up from *4*!"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Damon@21:1/5 to George Neuner on Tue Mar 28 21:31:13 2023
    On 3/28/23 11:17 AM, George Neuner wrote:
    On Mon, 27 Mar 2023 16:18:51 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a
    particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.


    The stack means it's not a FA.

    No, it still is an FA ... it just is a specialized form.

    The key point is that when talking about "class" of machines, stacks are generally considered at least "effectively" infinite. So a stack based
    machine.

    Give a FA an (unbounded) stack, and you have a different class of
    machine. Yes, in practice, you normally have a limit on the size of the
    stack, but it is generally big enough to handle the problem at hand.

    In the same way, a computer with 16 GB of ram and a TB of storage is
    still technically a FA, but normally isn't treated as one, as the
    methods to analyize a FA are impractical at that scale.


    Clifford Heath.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From George Neuner@21:1/5 to no.spam@please.net on Tue Mar 28 21:27:49 2023
    On Wed, 29 Mar 2023 09:00:34 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 29/03/23 02:17, George Neuner wrote:
    On Mon, 27 Mar 2023 16:18:51 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of
    parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a
    particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.

    The stack means it's not a FA.

    No, it still is an FA ... it just is a specialized form.

    Ok, it's an FA operating on a stack. The stack makes the whole thing >non-regular, aka infinite, so it's only an FA if you exclude the stack
    from the machine.

    Stackless FA, in fact, can process LR(1) grammars ... they just need
    (typically many) more states in the machine to do so


    No. A stack is not finite.

    Nor is the input stream. So what? The stack is NOT part of the
    machine, it is a memory used BY the state machine.


    Every FA is finite, that's why they're called
    FA. If you want to process a regular language, you can use an FA. If you
    want to process an irregular language, you cannot - you need somewhere
    to store unbounded staes and an FA *cannot* do that. It's in the
    definition of such things!

    Nowhere in the definition of finite automaton does it say the
    automaton is limited to what can be encoded by its states. In
    particular there is no prohibition against using an external memory.
    Recall that Turing machines used tapes of infinite length.

    In any event, I'm still not following why you think this somehow is
    important.


    ... and you'll
    get parse errors that probably don't really tell you what is wrong with
    the input :P.

    You can't rely on the tool for error handling (or even just messages)
    ... you really need to add deliberate error handling.

    I wasn't talking about error recovery, just about reporting. Both are
    hugely easier in an LL grammar. In the PEG parsers that I favour, you
    can almost always just report the rules on the stack at the furthest
    point reached, and (in all the grammars I've implemented) that gives a
    better error report than anything you'd bother to create manually.

    I wasn't talking about recovery either. When using an LR parser the
    grammar designer/implementer has to augment BOTH error reporting and
    error handling - which may or may not involve "recovery". See next.


    It amuses me that the folk who understand grammar well enough to be able
    to produce powerful parser generators seem to be universally incapable
    of generating code that can report parse failures in plain language. >Something about their brain's language centres has become so esoteric
    that normal language escapes them.

    LR works by incrementally assembling a sequence of tokens and looking
    for a pattern that matches it.

    LL works by selecting a pattern and incrementally looking to match
    that pattern with the sequence of tokens beginning at the current
    position in the input. Of course the pattern may be an alternation
    having multiple possibilities, but the principle of operation remains.

    Very, very different.

    Neither method innately knows the context when a pattern match fails,
    but in LL the context is readily apparent from the driver code which
    directs the parse, so it is easy to provide a (somewhat) meaningful
    error message just by maintaining a stack of the non-terminals already
    matched and dumping the last N entries.

    In contrast, in LR the context of the current match is given by the
    machine state and the stack of unreduced (as-yet unmatched) tokens.
    There is nothing readily available that could be used to provide a
    user meaningful message ... you'd have to examine the machine state to
    figure out even what you /might/ be looking for. Your position in the
    input is about as close as you can get to a meaningful message without
    the user code manually tracking context.


    Lex/Flex on the other hand exists to process only finite
    states. The FSM algorithms they use are more efficient than any
    algorithm that can handle LALR2, which is why these tools still exist as >>> independent tools.

    They exist separately because they were intended for different tasks

    The articles published at the time I first used them (in 1980) clearly
    stated that the two tools were needed "because we don't have a single >algorithm that is equally efficient at both tokenisation and parsing".

    That was true, but wasn't the reason AT THE TIME they were written.
    They were separate first and foremost because they were written at
    different times. They never were combined because most machines of
    that time did not have enough memory to handle the analysis and
    recognizer generation for even moderately complex grammars ... making
    the tool larger by including lexing was out of the question.

    After a while, it was simply inertia that kept them from being
    combined. Everyone was used to the status quo and so even when memory
    sizes grew to the point where having a combination tool could be
    useful, very few people cared.

    Inertia is the reason why a lot of potentially interesting things
    never happened. Diversion is the other reason - the people who could
    have done it were doing other things.


    Ken Thompson's implementation in the mid 1960s (documented in a 1968
    CACM paper) translated the regexp into machine code. The list of
    possible states was just a sequence of function call instructions.

    Yes, but Thompson's method was not widely used - again because of
    memory sizes. Most uses of regex used many patterns, and it was more
    efficient (memory-wise) to simply interpret the pattern directly: one
    driver function to handle N patterns.

    The technique of converting multiple NFAs into a single DFA has also
    been in use since the early 70s.

    Yes, and lex is from ~1973, IIRC. It was the first /publically/
    available tool able to combine multiple NDFAs into single DFA.


    ANTLR implements LL(*) which is LL with unbounded lookahead.

    It's unbounded, but must be regular. Many languages (including my >Constellation Query Language) require unbounded non-regular look-ahead,
    which PEG provides, at some extra cost in memory. But the pathological
    cases which *require* memoization only occur rarely, so a global packrat >strategy is sub-optimal.

    There
    are other LL(k) tools which require the programmer to choose a fixed
    amount of lookahead (and fail to process the grammar if the k value is
    too small). ANTLR analyzes the grammar and computes what lookahead is
    required pattern by pattern.

    That's a poor description of how it works. It looks ahead using an FA,
    so lookahead must be regular ("Finite State").

    No. Lookahead (and backtracking both) simply requires maintaining a
    queue of as-yet unmatched tokens. It certainly could be done by a
    state machine, but it does NOT require a state machine.


    Anyone interested in the overlap between regular languages and finite
    state machines should refer to the excellent
    <https://github.com/katef/libfsm>.

    Did you look at the FSM on the main README page of that site?
    It shows two RE's being combined into one DFA. Very neat stuff.

    I haven't examined their method. It may be that they have found some particularly efficient way to do it. That would be great. But
    algorithms for merging FAs in graph representation have been around at
    least since the 60s.


    I'm currently building a generalised parsing engine that also has the
    capability of processing arbitrary binary file and network stream
    formats, using a VM approach that interprets something very like a BNF,
    but in prefix notation (+a means one-or-more "a"s, not a+). It's tiny,
    efficient, embeddable, but can take a protocol description in a very few >>> bytes of VM code to handle almost any new protocol or format. I don't
    think that has been done before, and I've wanted to do it for 25 years.

    I would be interested to see that (when it's finished, of course).
    Good luck!

    The putative grammar for Px is here (but this doesn't describe captures >fully):
    <https://github.com/cjheath/strpp/blob/main/grammars/px.px>

    and the Pegexp engine is here (a template that I'm specialising to add >non-regular aka full LL grammar capability): ><https://github.com/cjheath/strpp/blob/main/include/pegexp.h>

    The Px grammar rewritten as a named-map of Pegexp expressions is here: ><https://github.com/cjheath/strpp/blob/main/test/peg_test.cpp#L55-L91>
    but I'll use a better structure for a compiled Px grammar, so that names >don't need to be looked up at runtime.

    I've almost finished dicking with the structure of input streams that
    will make it feasible for this to process data directly arriving on a
    socket, and only caching as much as is needed for back-up and retry.
    It's also possible to compile with/without UTF-8 support, but I can make
    that more convenient. It's possible to specify binary matching even in a >Unicode parser though.

    I want captures to do things like turn the ASCII digits on an HTTP >Content-Length header into a binary integer, save that integer as a
    capture variable, and use that variable to count bytes in a later
    repetition. This will enable a simple grammar describe all of HTTP/2.

    By nesting parsers (incrementally feeding capture sections to a nested >parser) it should be possible to for example, run a protocol engine that >generates an HTTP/2 request (generating from an HTTP request grammar),
    parses the response chunks, feeds base64-encoded chunks into a
    conversion function (not specified in Px), and the output of that
    conversion into e.g. a JPEG parser that actually verifies the JPEG
    format, and can e.g. extract (as a parse capture) the GPS location from >inside the Exif data attached... and all without having to extend or >recompile the engine. Just load the target grammar, and if it succeeds,
    you get the GPS location... and all file formats have been validated.

    I envisage a world where the file-system is type-safe; almost no file is
    a pure byte-stream, and it's not possible to save a JPEG file that
    doesn't match the JPEG syntax. The file system must be pre-loaded with a >grammar for every new file type before writing such a file.

    Clifford Heath.

    George

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Clifford Heath@21:1/5 to George Neuner on Thu Mar 30 08:33:16 2023
    On 29/03/23 12:27, George Neuner wrote:
    On Wed, 29 Mar 2023 09:00:34 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 29/03/23 02:17, George Neuner wrote:
    On Mon, 27 Mar 2023 16:18:51 +1100, Clifford Heath
    <no.spam@please.net> wrote:

    On 26/03/23 15:45, George Neuner wrote:
    On Wed, 22 Mar 2023 18:15:43 -0700, Don Y
    <blockedofcourse@foo.invalid> wrote:
    The terms "FA" (finite automaton) and "FSM" (finite state machine)
    are, in fact, synonymous.

    What is confusing is that we got to this point through discussion of >>>>> parsing and lexing tools - which ARE geared toward languages.
    Moreover, yacc and bison do NOT implement a general FA, but rather a >>>>> particular variety of FA that useful for language parsing and which
    involves an auxiliary stack.

    The stack means it's not a FA.

    No, it still is an FA ... it just is a specialized form.

    Ok, it's an FA operating on a stack. The stack makes the whole thing
    non-regular, aka infinite, so it's only an FA if you exclude the stack >>from the machine.

    Stackless FA, in fact, can process LR(1) grammars ... they just need
    (typically many) more states in the machine to do so


    No. A stack is not finite.

    Nor is the input stream. So what? The stack is NOT part of the
    machine, it is a memory used BY the state machine.


    Every FA is finite, that's why they're called
    FA. If you want to process a regular language, you can use an FA. If you
    want to process an irregular language, you cannot - you need somewhere
    to store unbounded staes and an FA *cannot* do that. It's in the
    definition of such things!

    Nowhere in the definition of finite automaton does it say the
    automaton is limited to what can be encoded by its states. In
    particular there is no prohibition against using an external memory.
    Recall that Turing machines used tapes of infinite length.

    In any event, I'm still not following why you think this somehow is important.


    ... and you'll
    get parse errors that probably don't really tell you what is wrong with >>>> the input :P.

    You can't rely on the tool for error handling (or even just messages)
    ... you really need to add deliberate error handling.

    I wasn't talking about error recovery, just about reporting. Both are
    hugely easier in an LL grammar. In the PEG parsers that I favour, you
    can almost always just report the rules on the stack at the furthest
    point reached, and (in all the grammars I've implemented) that gives a
    better error report than anything you'd bother to create manually.

    I wasn't talking about recovery either. When using an LR parser the
    grammar designer/implementer has to augment BOTH error reporting and
    error handling - which may or may not involve "recovery". See next.


    It amuses me that the folk who understand grammar well enough to be able
    to produce powerful parser generators seem to be universally incapable
    of generating code that can report parse failures in plain language.
    Something about their brain's language centres has become so esoteric
    that normal language escapes them.

    LR works by incrementally assembling a sequence of tokens and looking
    for a pattern that matches it.

    LL works by selecting a pattern and incrementally looking to match
    that pattern with the sequence of tokens beginning at the current
    position in the input. Of course the pattern may be an alternation
    having multiple possibilities, but the principle of operation remains.

    Very, very different.

    Neither method innately knows the context when a pattern match fails,
    but in LL the context is readily apparent from the driver code which
    directs the parse, so it is easy to provide a (somewhat) meaningful
    error message just by maintaining a stack of the non-terminals already matched and dumping the last N entries.

    In contrast, in LR the context of the current match is given by the
    machine state and the stack of unreduced (as-yet unmatched) tokens.
    There is nothing readily available that could be used to provide a
    user meaningful message ... you'd have to examine the machine state to
    figure out even what you /might/ be looking for. Your position in the
    input is about as close as you can get to a meaningful message without
    the user code manually tracking context.


    Lex/Flex on the other hand exists to process only finite
    states. The FSM algorithms they use are more efficient than any
    algorithm that can handle LALR2, which is why these tools still exist as >>>> independent tools.

    They exist separately because they were intended for different tasks

    The articles published at the time I first used them (in 1980) clearly
    stated that the two tools were needed "because we don't have a single
    algorithm that is equally efficient at both tokenisation and parsing".

    That was true, but wasn't the reason AT THE TIME they were written.
    They were separate first and foremost because they were written at
    different times. They never were combined because most machines of
    that time did not have enough memory to handle the analysis and
    recognizer generation for even moderately complex grammars ... making
    the tool larger by including lexing was out of the question.

    After a while, it was simply inertia that kept them from being
    combined. Everyone was used to the status quo and so even when memory
    sizes grew to the point where having a combination tool could be
    useful, very few people cared.

    Inertia is the reason why a lot of potentially interesting things
    never happened. Diversion is the other reason - the people who could
    have done it were doing other things.


    Ken Thompson's implementation in the mid 1960s (documented in a 1968
    CACM paper) translated the regexp into machine code. The list of
    possible states was just a sequence of function call instructions.

    Yes, but Thompson's method was not widely used - again because of
    memory sizes. Most uses of regex used many patterns, and it was more efficient (memory-wise) to simply interpret the pattern directly: one
    driver function to handle N patterns.

    The technique of converting multiple NFAs into a single DFA has also
    been in use since the early 70s.

    Yes, and lex is from ~1973, IIRC. It was the first /publically/
    available tool able to combine multiple NDFAs into single DFA.


    ANTLR implements LL(*) which is LL with unbounded lookahead.

    It's unbounded, but must be regular. Many languages (including my
    Constellation Query Language) require unbounded non-regular look-ahead,
    which PEG provides, at some extra cost in memory. But the pathological
    cases which *require* memoization only occur rarely, so a global packrat
    strategy is sub-optimal.

    There
    are other LL(k) tools which require the programmer to choose a fixed
    amount of lookahead (and fail to process the grammar if the k value is
    too small). ANTLR analyzes the grammar and computes what lookahead is
    required pattern by pattern.

    That's a poor description of how it works. It looks ahead using an FA,
    so lookahead must be regular ("Finite State").

    No. Lookahead (and backtracking both) simply requires maintaining a
    queue of as-yet unmatched tokens. It certainly could be done by a
    state machine, but it does NOT require a state machine.


    Anyone interested in the overlap between regular languages and finite
    state machines should refer to the excellent
    <https://github.com/katef/libfsm>.

    Did you look at the FSM on the main README page of that site?
    It shows two RE's being combined into one DFA. Very neat stuff.

    I haven't examined their method. It may be that they have found some particularly efficient way to do it. That would be great. But
    algorithms for merging FAs in graph representation have been around at
    least since the 60s.


    I'm currently building a generalised parsing engine that also has the
    capability of processing arbitrary binary file and network stream
    formats, using a VM approach that interprets something very like a BNF, >>>> but in prefix notation (+a means one-or-more "a"s, not a+). It's tiny, >>>> efficient, embeddable, but can take a protocol description in a very few >>>> bytes of VM code to handle almost any new protocol or format. I don't
    think that has been done before, and I've wanted to do it for 25 years. >>>
    I would be interested to see that (when it's finished, of course).
    Good luck!

    The putative grammar for Px is here (but this doesn't describe captures
    fully):
    <https://github.com/cjheath/strpp/blob/main/grammars/px.px>

    and the Pegexp engine is here (a template that I'm specialising to add
    non-regular aka full LL grammar capability):
    <https://github.com/cjheath/strpp/blob/main/include/pegexp.h>

    The Px grammar rewritten as a named-map of Pegexp expressions is here:
    <https://github.com/cjheath/strpp/blob/main/test/peg_test.cpp#L55-L91>
    but I'll use a better structure for a compiled Px grammar, so that names
    don't need to be looked up at runtime.

    I've almost finished dicking with the structure of input streams that
    will make it feasible for this to process data directly arriving on a
    socket, and only caching as much as is needed for back-up and retry.
    It's also possible to compile with/without UTF-8 support, but I can make
    that more convenient. It's possible to specify binary matching even in a
    Unicode parser though.

    I want captures to do things like turn the ASCII digits on an HTTP
    Content-Length header into a binary integer, save that integer as a
    capture variable, and use that variable to count bytes in a later
    repetition. This will enable a simple grammar describe all of HTTP/2.

    By nesting parsers (incrementally feeding capture sections to a nested
    parser) it should be possible to for example, run a protocol engine that
    generates an HTTP/2 request (generating from an HTTP request grammar),
    parses the response chunks, feeds base64-encoded chunks into a
    conversion function (not specified in Px), and the output of that
    conversion into e.g. a JPEG parser that actually verifies the JPEG
    format, and can e.g. extract (as a parse capture) the GPS location from
    inside the Exif data attached... and all without having to extend or
    recompile the engine. Just load the target grammar, and if it succeeds,
    you get the GPS location... and all file formats have been validated.

    I envisage a world where the file-system is type-safe; almost no file is
    a pure byte-stream, and it's not possible to save a JPEG file that
    doesn't match the JPEG syntax. The file system must be pre-loaded with a
    grammar for every new file type before writing such a file.

    Clifford Heath.

    George


    Thank for your complete lack of insightful comments on what I offered,
    and the completely unnecessary lesson on subjects I'm already quite
    familiar with (even if some of my memories are a bit flakey).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)